На этом шаге рассмотрим пример создания простого HTTP-сервера.
Создайте веб-приложение, предлагающее пользователю вводить числа и выполнять простейшие вычисления (рис. 1).
Рис.1. Пример работы веб-приложения
Раскрыть/скрыть решение и комментарии.
pageTop = `<!DOCTYPE HTML><html><head> <style>.error{color:#FF0000;}</style></head><title>Калькулятор</title> <body><h3>Арифметические операции</h3> <p>Выполнение некоторых арифметических операций</p>` form = `<form action="/" method="POST"> <label for="number1">Введите первое число:</label><br /> <input type="text" name="a" size="10"><br /> <label for="number2">Введите второе число:</label><br /> <input type="text" name="b" size="10"><br /> <input type="submit" value="Выполнить"> </form>` pageBottom = `</body></html>` error = `<p class="error">%s</p>`
Раскрыть/скрыть решение и комментарии.
http.HandleFunc("/", homePage)) /*Функция http.HandleFunc() принимает два аргумента: путь и ссылку на функцию, которую следует вызвать при поступлении запроса по указанному пути. Функция должна иметь сигнатуру func(http.ResponseWriter, *http.Request). Приложение может зарегистрировать неограниченное количество пар путь/функция. Здесь регистрируются путь / (т.е. домашняя страница веб-приложения) и пользовательская функция homePage()*/ if err := http.ListenAndServe(":9001", nil); err != nil {/*Функция http.ListenAndServe() запускает веб-сервер, принимающий запросы на указанном сетевом TCP-адресе, – здесь используется локальный адрес компьютера и порт с номером 9001. Когда указывается только номер порта, автоматически предполагается, что адрес соответствует локальному компьютеру, можно было использовать адрес "localhost:9001" или "127.0.0.1:9001". Номер порта для данного приложения был выбран совершенно произвольно, вместо него можно указать другой, если использование этого номера вызывает конфликты с существующим сервером. Второй аргумент определяет тип сервера. Обычно в нем передается nil, чтобы выбрать тип по умолчанию*/ log.Fatal("Не удалось запустить сервер", err) }
Раскрыть/скрыть решение и комментарии.
/*Эта функция вызывается при каждом посещении веб-сайта приложения. В аргументе writer передается значение, куда должен записываться ответ (в формате HTML), а в аргументе request – подробная информация о запросе*/ func homePage(writer http.ResponseWriter, request *http.Request) { err := request.ParseForm() //Должна вызываться перед записью в ответ /*Функция начинается с анализа формы (которая изначально имеет пустые текстовые элементы <input>). Текстовым элементам <input> присвоены имена "a" и "b", чтобы на них можно было ссылаться позднее, при обработке формы. Атрибуту action формы присвоено значение /, чтобы после щелчка на кнопке Выполнить браузер запрашивал ту же самую страницу. Это означает, что функция homePage() будет вызываться для обработки всех запросов, поэтому она должна уметь обрабатывать первичный вызов, когда еще не было введено ни одного числа, и последующие вызовы, когда числа были введены или когда возникла ошибка. Фактически вся работа выполняется функцией processRequest(), поэтому разные случаи обрабатываются этой функцией. После анализа формы выводятся строковые константы pageTop и form. Если в ходе анализа обнаружится ошибка, также будет выведено сообщение об ошибке; anError – это строка формата, а err – значение ошибки для форматирования*/ fmt.Fprint(writer, pageTop, form) if err != nil { fmt.Fprintf(writer, error, err) } else { /*В случае успешного выполнения анализа вызывается функция processRequest(), извлекающая числа, введенные пользователем*/ if nums, message, ok := processRequest(request); ok { /*Если все числа окажутся допустимыми, вызовом функций вычисляются и выводятся в форматированном виде арифметические операции; иначе будет выведено сообщение об ошибке*/ add := addition(nums) //сумма чисел sub := subtraction(nums) //разность чисел mul := multiplication(nums) //произведение чисел div, str := division(nums) /*деление чисел (или сообщение, что на 0 делить нельзя)*/ answer := formatSolutions(nums, add, sub, mul, div, str) //формирование ответа (таблица с результатами) fmt.Fprintf(writer, answer) //вывод ответа } else if message != "" { //если возникла ошибка fmt.Fprintf(writer, error, message) } } fmt.Fprint(writer, pageBottom) }
Раскрыть/скрыть решение и комментарии.
/*Функция возвращает введенные числа (массив), строку, если возникает ошибка и логическое значение. Эта функция читает данные формы из значения request*/ func processRequest(request *http.Request) ([2]float64, string, bool) { var floats [2]float64 //массив с вещественными числами count := 0 //количество прочитанных чисел for index, key := range []string{"a", "b"} { /*Значение request имеет поле Form типа map[string][]string. Это означает, что ключи отображения являются строками, а значения – срезами со строками. То есть одному ключу соответствует значение, содержащее произвольное количество строк. Функция проверяет наличие ключа "key" (значения определены во внешнем цикле) и, если он присутствует и его значение содержит хотя бы одну строку, можно быть уверенными, что в форме имеются числа для чтения*/ if slice, found := request.Form[key]; found && len(slice) > 0 { if slice[0] != "" { /*Для каждой строки предпринимается попытка преобразовать строку в значение типа float64 с помощью функции strconv.ParseFloat(), принимающей строку и размер результата в битах, 32 или 64. Если в процессе преобразования возникнет ошибка, вызывающей программе немедленно возвращаются все числа типа float64, которые удалось получить, непустое сообщение об ошибке и значение false*/ if x, err := strconv.ParseFloat(slice[0], 64); err != nil { return floats, "'" + slice[0] + "' ошибка", false } else { /*В случае успешного преобразования полученное число типа float64 добавляется в массив floats*/ floats[index] = x } } else { /*Если поля для ввода чисел были пусты при очередном нажатии кнопки Выполнить, то числам будут присвоены нулевые значения*/ request.Form[key][0] = "0" floats[index] = 0 } count++ } } /*Если форма отображается первый раз, элементы <input> с именами "a" и "b" еще пусты. Это не является ошибкой, поэтому возвращается пустой срез со значениями типа float64, пустое сообщение об ошибке и false, чтобы показать, что статистические характеристики не были вычислены, – в результате этого будет отображена пустая форма*/ if count != 2 { // при первом запуске поля пустые return floats, "", false //это не ошибка } else { // но вычислять нечего /*Если пользователь ввел некоторые данные, возвращается срез со значениями типа float64, пустое сообщение об ошибке и true*/ return floats, "", true } }
Раскрыть/скрыть решение и комментарии.
/*Функция принимает в качестве аргумента массив из введенных чисел и возвращает результат в формате float64 и строку, которая пуста, когда операция деления допустима, иначе, принимает значение "Деление на 0"*/ func division(floats [2]float64) (float64, string) { a, b := floats[0], floats[1] /*переменные принимают значения первого и второго числа соответственно*/ if b == 0 { return 0, "Деление на 0" } else { return a / b, "" } }
Раскрыть/скрыть решение и комментарии.
func formatSolutions(x [2]float64, a, b, c, d float64, str string) string { if str == "" { return fmt.Sprintf(`<table border="1"> <tr><th colspan="2">Результат</th></tr> <tr><td>Число a</td><td>%f</td></tr> <tr><td>Число b</td><td>%f</td></tr> <tr><td>Сложение (a+b)</td><td>%f</td></tr> <tr><td>Вычитание (a-b)</td><td>%f</td></tr> <tr><td>Умножение (a*b)</td><td>%f</td></tr> <tr><td>Деление (a/b)</td><td>%f</td></tr> </table>`, x[0], x[1], a, b, c, d) } else { return fmt.Sprintf(`<table border="1"> <tr><th colspan="2">Результат</th></tr> <tr><td>Число a</td><td>%f</td></tr> <tr><td>Число b</td><td>%f</td></tr> <tr><td>Сложение (a+b)</td><td>%f</td></tr> <tr><td>Вычитание (a-b)</td><td>%f</td></tr> <tr><td>Умножение (a*b)</td><td>%f</td></tr> <tr><td>Деление (a/b)</td><td>%s</td></tr> </table>`, x[0], x[1], a, b, c, str) } }
Архив примера можно взять здесь.
На следующем шаге рассмотрим некоторые особенности работы со строками в Go.