Хабрахабр

[Из песочницы] Как создать свое первое веб-приложение с помощью Go

Представляю вашему вниманию перевод статьи "How to build your first web application with Go" автора Ayooluwa Isaiah. Привет, Хабр!

Мы создадим новостное приложение, которое использует News API для получения новостных статей по определенной теме, и развернём его на продакшн сервере в конце. Это руководство к вашему первому веб-приложению на Go.

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

Требования

Версия Go, которую я использовал при создании приложения, также является самой последней на момент написания: 1. Единственное требование для этого задания — чтобы на вашем компьютере был установлен Go, и вы немного знакомы с его синтаксисом и конструкциями. 9. 12. Чтобы просмотреть установленную версию Go, используйте команду go version.

Если вы считаете это задание слишком сложным для вас, перейдите к моему предыдущему вводному уроку по языку, который должен помочь вам освоиться.

Итак, начнем!

У нас есть три основных файла: В файле main.go мы напишем весь код Go для этого задания. Клонируем репозиторий стартовых файлов на GitHub и cd в созданный каталог. Файл index.html — это шаблон, который будет отправлен в браузер, а стили для приложения находятся в assets/styles.css.

Создадим базовый веб-сервер

Измените ваш файл main.go так, чтобы он выглядел следующим образом: Давайте начнем с создания базового сервера, который отправляет текст «Hello World!» в браузер при выполнении запроса GET к корню сервера.

package main import ( "net/http" "os"
) func indexHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("<h1>Hello World!</h1>"))
} func main() mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux)
}

После этого мы импортировали пакет net/http, который предоставляет реализации клиента и сервера HTTP для использования в нашем приложении. Первая строка package main — декларирует, что код в файле main.go принадлежит главному пакету. Этот пакет является частью стандартной библиотеки и входит в каждую установку Go.

NewServeMux() создает новый мультиплексор HTTP-запросов и присваивает его переменной mux. В функции main, http. По сути, мультиплексор запросов сопоставляет URL-адрес входящих запросов со списком зарегистрированных путей и вызывает соответствующий обработчик для пути всякий раз, когда найдено совпадение.

Эта функция-обработчик является вторым аргументом для HandleFunc и всегда имеет сигнатуру func (w http. Далее мы регистрируем нашу первую функцию-обработчик для корневого пути /. Request). ResponseWriter, r * http.

Параметр w — это структура, которую мы используем для отправки ответов на HTTP-запрос. Если вы посмотрите на функцию indexHandler, вы увидите, что она имеет именно такую сигнатуру, что делает ее действительным вторым аргументом для HandleFunc. Она реализует метод Write(), который принимает слайс байтов и записывает объединенные данные как часть HTTP-ответа.

Это то, как мы получаем доступ к данным, отправляемым веб-браузером на сервере. С другой стороны, параметр r представляет HTTP-запрос, полученный от клиента. Мы еще не используем его здесь, но мы точно будем использовать его позже.

ListenAndServe(), который запускает сервер на порту 3000, если порт не установлен окружением. Наконец, у нас есть метод http. Не стесняйтесь использовать другой порт, если 3000 используется на вашем компьютере.

Затем скомпилируйте и выполните код, который вы только что написали:

go run main.go

Если вы перейдете на http: // localhost: 3000 в своем браузере, вы должны увидеть текст «Hello World!».

Brave browser showing Hello World text

Шаблоны в Go

Если вы знакомы с шаблонами на других языках, это должно быть достаточно просто для понимания. Давайте рассмотрим основы шаблонизации в Go.

Например, мы можем создать шаблон для панели навигации и использовать его на всех страницах сайта, не дублируя код. Шаблоны предоставляют простой способ настроить вывод вашего веб-приложения в зависимости от маршрута без необходимости писать один и тот же код в разных местах. Кроме того, мы также получаем возможность добавить некоторую базовую логику на наши веб-страницы.

Оба предоставляют один и тот же интерфейс, однако пакет html/template используется для генерации HTML-вывода, который защищен от инъекций кода, поэтому мы будем использовать его здесь. Go предоставляет две библиотеки шаблонов в своей стандартной библиотеке: text/template и html/template.

Импортируйте этот пакет в ваш файл main.go и используйте его следующим образом:

package main import ( "html/template" "net/http" "os"
) var tpl = template.Must(template.ParseFiles("index.html")) func indexHandler(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, nil)
} func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux)
}

Вызов template. tpl — переменная уровня пакета, которая указывает на определение шаблона из предоставленных файлов. ParseFiles анализирует файлindex.html в корне каталога нашего проекта и проверяет его на валидность.

ParseFiles в template. Мы оборачиваем вызов template. Причина, по которой мы паникуем здесь вместо того, чтобы пытаться обработать ошибку, заключается в том, что нет смысла продолжать выполнение кода, если у нас невалидный шаблон. Must, чтобы код вызывал панику при возникновении ошибки. Это проблема, которая должна быть устранена перед попыткой перезапустить сервер.

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

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

Вы должны увидеть текст «News App Demo» на странице, как показано ниже: Остановите запущенный процесс в вашем терминале с помощью Ctrl-C и запустите его снова с помощью go run main.go, затем обновите ваш браузер.

Brave browser showing News App Demo Text

Добавляем панель навигации на страницу

Замените содержимое тега <body> в вашем файле index.html, как показано ниже:

<main> <header> <a class="logo" href="/">News Demo</a> <form action="/search" method="GET"> <input autofocus class="search-input" value="" placeholder="Enter a news topic" type="search" name="q"> </form> <a href="https://github.com/freshman-tech/news" class="button github-button">View on Github</a> </header>
</main>

Вы должны увидеть что-то похожее на это: Затем перезагрузите сервер и обновите ваш браузер.

Browser showing unstyled navigation bar

Работа со статическими файлами

Обратите внимание, что панель навигации, которую мы добавили выше, не имеет стилей, несмотря на тот факт, что мы уже указали их в <head> нашего документа.

Поэтому, если вы перейдете на http://localhost:3000/assets/style.css, вы все равно получите домашнюю страницу News Demo вместо файла CSS, потому что маршрут /assets/style.css не был объявлен специально. Это потому, что путь / фактически совпадает со всеми путями, которые не обрабатываются в другом месте.

К счастью, мы можем создать один обработчик для обслуживания всех статических ресурсов. Но необходимость объявлять явные обработчики для всех наших статических файлов нереальна и не может масштабироваться.

Первое, что нужно сделать, — создать экземпляр объекта файлового сервера, передав каталог, в котором находятся все наши статические файлы:

fs := http.FileServer(http.Dir("assets"))

Далее нам нужно указать нашему маршрутизатору использовать этот объект файлового сервера для всех путей, начинающихся с префикса /assets/:

mux.Handle("/assets/", http.StripPrefix("/assets/", fs))

Теперь всё вместе:

// main.go // начало файла func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() // Добавьте следующие две строки fs := http.FileServer(http.Dir("assets")) mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux)
}

Стили должны включиться, как показано ниже: Перезагрузите сервер и обновите браузер.

Brave browser showing styled navigation bar

Мы будем использовать News API для обработки запросов, поэтому вам нужно зарегистрироваться для получения бесплатного ключа API здесь. Давайте создадим роут, который обрабатывает поисковые запросы для новостных статей.

Этот параметр page является необязательным. Этот маршрут ожидает два параметра запроса: q представляет запрос пользователя, а page используется для пролистывания результатов. Если он не включен в URL, мы просто предположим, что номер страницы результатов имеет значение «1».

Добавьте следующий обработчик под indexHandler в ваш файлmain.go:

func searchHandler(w http.ResponseWriter, r *http.Request) { u, err := url.Parse(r.URL.String()) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Internal server error")) return } params := u.Query() searchKey := params.Get("q") page := params.Get("page") if page == "" { page = "1" } fmt.Println("Search Query is: ", searchKey) fmt.Println("Results page is: ", page)
}

Приведенный выше код извлекает параметры q и page из URL-адреса запроса и выводит их оба в терминал.

Затем зарегистрируйте функцию searchHandler в качестве обработчика пути/search, как показано ниже:

func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } mux := http.NewServeMux() fs := http.FileServer(http.Dir("assets")) mux.Handle("/assets/", http.StripPrefix("/assets/", fs)) // Add the next line mux.HandleFunc("/search", searchHandler) mux.HandleFunc("/", indexHandler) http.ListenAndServe(":"+port, mux)
}

Не забудьте импортировать пакеты fmt иnet/url сверху:

import ( "fmt" "html/template" "net/http" "net/url" "os"
)

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

Создаём модель данных

Когда мы делаем запрос к конечной точке News API/everything, мы ожидаем ответ json в следующем формате:

{ "status": "ok", "totalResults": 4661, "articles": [ { "source": { "id": null, "name": "Gizmodo.com" }, "author": "Jennings Brown", "title": "World's Dumbest Bitcoin Scammer Tries to Scam Bitcoin Educator, Gets Scammed in The Process", "description": "Ben Perrin is a Canadian cryptocurrency enthusiast and educator who hosts a bitcoin show on YouTube. This is immediately apparent after a quick a look at all his social media. Ten seconds of viewing on of his videos will show that he is knowledgeable about di…", "url": "https://gizmodo.com/worlds-dumbest-bitcoin-scammer-tries-to-scam-bitcoin-ed-1837032058", "urlToImage": "https://i.kinja-img.com/gawker-media/image/upload/s--uLIW_Oxp--/c_fill,fl_progressive,g_center,h_900,q_80,w_1600/s4us4gembzxlsjrkmnbi.png", "publishedAt": "2019-08-07T16:30:00Z", "content": "Ben Perrin is a Canadian cryptocurrency enthusiast and educator who hosts a bitcoin show on YouTube. This is immediately apparent after a quick a look at all his social media. Ten seconds of viewing on of his videos will show that he is knowledgeable about..." } ]
}

Конечно, вы можете сделать это вручную, но я предпочитаю использовать веб-сайт JSON-to-Go, который делает этот процесс действительно простым. Чтобы работать с этими данными в Go, нам нужно сгенерировать структуру, которая отражает данные при декодировании тела ответа. Он генерирует структуру Go (с тегами), которая будет работать для этого JSON.

Вот что мы получаем для вышеуказанного объекта JSON: Все, что вам нужно сделать, это скопировать объект JSON и вставить его в поле, помеченное JSON, затем скопировать вывод и вставить его в свой код.

type AutoGenerated struct { Status string `json:"status"` TotalResults int `json:"totalResults"` Articles []struct { Source struct { ID interface{} `json:"id"` Name string `json:"name"` } `json:"source"` Author string `json:"author"` Title string `json:"title"` Description string `json:"description"` URL string `json:"url"` URLToImage string `json:"urlToImage"` PublishedAt time.Time `json:"publishedAt"` Content string `json:"content"` } `json:"articles"`
}

Brave browser showing JSON to Go tool

Вставьте следующее ниже объявление переменной tpl в main.go и добавьте пакет time в ваш импорт: Я сделал несколько изменений в структуре AutoGenerated, отделив фрагмент Articles в его собственную структуру и обновив имя структуры.

type Source struct { ID interface{} `json:"id"` Name string `json:"name"`
} type Article struct { Source Source `json:"source"` Author string `json:"author"` Title string `json:"title"` Description string `json:"description"` URL string `json:"url"` URLToImage string `json:"urlToImage"` PublishedAt time.Time `json:"publishedAt"` Content string `json:"content"`
} type Results struct { Status string `json:"status"` TotalResults int `json:"totalResults"` Articles []Article `json:"articles"`
}

Однако принято представлять поля JSON с помощью camelCase или snake_case, которые не начинаются с заглавной буквы. Как вы, возможно, знаете, Go требует, чтобы все экспортируемые поля в структуре начинались с заглавной буквы.

Это также позволяет использовать совершенно разные имена для структурного поля и соответствующего поля json, если это необходимо. Поэтому мы используем теги поля структуры, такие как json:"id", чтобы явно отобразить поле структуры в поле JSON, как показано выше.

Добавьте это ниже структуры Results в main.go: Наконец, давайте создадим другой тип структуры для каждого поискового запроса.

type Search struct { SearchKey string NextPage int TotalPages int Results Results
}

SearchKey — это сам запрос, поле NextPage позволяет пролистывать результаты, TotalPages — общее количество страниц результатов запроса, а Results — текущая страница результатов запроса. Эта структура представляет собой каждый поисковый запрос, сделанный пользователем.

Отправляем запрос по News API и рендерим результаты

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

Переменные среды являются распространенным подходом, но я решил использовать вместо них флаги командной строки. Поскольку для News API требуется ключ API, нам нужно найти способ передать его в нашем приложении без жесткого кодирования в коде. Go предоставляет пакет flag, поддерживающий базовый анализ флагов командной строки, и это то, что мы собираемся использовать здесь.

Сначала объявите новую переменную apiKey под переменной tpl:

var apiKey *string

Затем используйте её в функции main следующим образом:

func main() { apiKey = flag.String("apikey", "", "Newsapi.org access key") flag.Parse() if *apiKey == "" { log.Fatal("apiKey must be set") } // остальная часть функции
}

String(), который позволяет нам определять строковый флаг. Здесь мы вызываем метод flag. Первый аргумент этого метода — имя флага, второй — значение по умолчанию, а третий — описание использования.

Parse(), чтобы фактически проанализировать их. После определения всех флагов вам нужно вызвать flag. Наконец, так как apikey является обязательным компонентом для этого приложения, мы обеспечиваем аварийное завершение программы, если этот флаг не установлен при выполнении программы.

Убедитесь, что вы добавили пакет flag в свой импорт, затем перезапустите сервер и передайте требуемый флаг apikey, как показано ниже:

go run main.go -apikey=<your newsapi access key>

Далее, давайте продолжим и обновим searchHandler, чтобы поисковый запрос пользователя отправлялся на newsapi.org и результаты отображались в нашем шаблоне.

Println() в конце функции searchHandler следующим кодом: Замените два вызова метода fmt.

func searchHandler(w http.ResponseWriter, r *http.Request) { // beginning of the function search := &Search{} search.SearchKey = searchKey next, err := strconv.Atoi(page) if err != nil { http.Error(w, "Unexpected server error", http.StatusInternalServerError) return } search.NextPage = next pageSize := 20 endpoint := fmt.Sprintf("https://newsapi.org/v2/everything?q=%s&pageSize=%d&page=%d&apiKey=%s&sortBy=publishedAt&language=en", url.QueryEscape(search.SearchKey), pageSize, search.NextPage, *apiKey) resp, err := http.Get(endpoint) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode != 200 { w.WriteHeader(http.StatusInternalServerError) return } err = json.NewDecoder(resp.Body).Decode(&search.Results) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize))) err = tpl.Execute(w, search) if err != nil { w.WriteHeader(http.StatusInternalServerError) }
}

Сначала мы создаем новый экземпляр структуры Search и устанавливаем значение поля SearchKey равным значению параметра URL q в HTTP-запросе.

Затем мы создаем переменную pageSize и устанавливаем ее значение равным 20. После этого мы конвертируем переменную page в целое число и присваиваем результат полю NextPage переменной search. Это значение может находиться в диапазоне от 0 до 100. Эта переменная pageSize представляет количество результатов, которые API новостей будет возвращать в своем ответе.

Sprintf() и делаем запрос GET к ней. Затем мы создаем конечную точку с помощью fmt. В противном случае тело ответа парсится в search. Если ответ от News API не 200 OK, мы вернем клиенту общую ошибку сервера. Results.

Например, если запрос возвращает 100 результатов, а мы одновременно просматриваем только 20, нам нужно будет пролистать пять страниц, чтобы просмотреть все 100 результатов по этому запросу. Затем мы вычисляем общее количество страниц путем деления поля TotalResults на pageSize.

Это позволяет нам получать доступ к данным из объекта JSON в нашем шаблоне, как вы увидите. После этого мы рендерим наш шаблон и передаем переменную search в качестве интерфейса данных.

Прежде чем перейти к index.html, обязательно обновите ваши импорты, как показано ниже:

import ( "encoding/json" "flag" "fmt" "html/template" "log" "math" "net/http" "net/url" "os" "strconv" "time"
)

Добавьте это под тегом <header>: Давайте продолжим и отобразим результаты на странице, изменив файл index.html следующим образом.

<section class="container"> <ul class="search-results"> {{ range .Results.Articles }} <li class="news-article"> <div> <a target="_blank" rel="noreferrer noopener" href="{{.URL}}"> <h3 class="title">{{.Title }}</h3> </a> <p class="description">{{ .Description }}</p> <div class="metadata"> <p class="source">{{ .Source.Name }}</p> <time class="published-date">{{ .PublishedAt }}</time> </div> </div> <img class="article-image" src="{{ .URLToImage }}"> </li> {{ end }} </ul>
</section>

Этот оператор ссылается на объект структуры (в данном случае search), а затем внутри шаблона мы просто указываем имя поля (как {{. Чтобы получить доступ к полю структуры в шаблоне, мы используем оператор точки. Results}}).

Здесь мы перебираем слайс структур Article, содержащихся в поле Articles, и выводим HTML на каждой итерации. Блок range позволяет нам перебирать слайс в Go и выводить некоторый HTML для каждого элемента в слайсе.

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

Browser showing news listings

Сохраняем поисковый запрос в инпуте

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

Мы можем легко это исправить, обновив атрибут value тега input в нашем файле index.html следующим образом:

<input autofocus class="search-input" value="{{ .SearchKey }}" placeholder="Enter a news topic" type="search" name="q">

Поисковый запрос будет сохранен, как показано ниже: Перезапустите браузер и выполните новый поиск.

Форматируем дату публикации

Текущий вывод показывает, как News API возвращает дату публикации статьи. Если вы посмотрите на дату в каждой статье, вы увидите, что она плохо читаема. Но мы можем легко изменить это, добавив метод в структуру Article и используя его для форматирования даты вместо использования значения по умолчанию.

Давайте добавим следующий код чуть ниже структуры Article в main.go:

func (a *Article) FormatPublishedDate() string { year, month, day := a.PublishedAt.Date() return fmt.Sprintf("%v %d, %d", month, day, year)
}

Здесь новый метод FormatPublishedDate создан в структуре Article, и этот метод форматирует поле PublishedAt в Article и возвращает строку в следующем формате: 10 января 2009.

PublishedAt на . Чтобы использовать этот новый метод в вашем шаблоне, замените . Затем перезагрузите сервер и повторите предыдущий поисковый запрос. FormatPublishedDate в вашем файле index.html. Это выведет результаты с правильно отформатированным временем, как показано ниже:

Brave browser showing correctly formatted date

Выводим общее количество результатов

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

Все, что вам нужно сделать, это добавить следующий код как дочерний элемент .container, чуть выше элемента .search-results в вашем файле index.html:

<div class="result-count"> {{ if (gt .Results.TotalResults 0)}} <p>About <strong>{{ .Results.TotalResults }}</strong> results were found.</p> {{ else if (ne .SearchKey "") and (eq .Results.TotalResults 0) }} <p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p> {{ end }}
</div>

Мы используем функцию gt, чтобы проверить, что поле TotalResults структуры Results больше нуля. Шаблоны Go поддерживают несколько функций сравнения, некоторые из которых используются выше. Если это так, общее количество результатов будет напечатано в верхней части страницы.

SearchKey "")) и TotalResults равно нулю ((eq . В противном случае, если SearchKey не равен пустой строке ((ne . TotalResults 0)), то выводится сообщение «No results found». Results.

На экране должно появиться сообщение «No results found». Перезапустите сервер и введите несколько слов в поле поиска, чтобы не было найдено новостей по вашему запросу.

Browser showing no results found message

Количество результатов будет выведено вверху страницы, как показано ниже: После этого сделайте еще один поисковый запрос на этот раз с популярной темой.

Browser showing results count at the top of the page

Так как мы отображаем только 20 результатов одновременно, нам нужен способ, чтобы пользователь мог перейти на следующую или предыдущую страницу результатов в любое время.

Чтобы определить, была ли достигнута последняя страница результатов, создайте этот новый метод ниже объявления структуры Search вmain.go: Сначала добавим кнопку Next внизу результатов, если последняя страница результатов еще не достигнута.

func (s *Search) IsLastPage() bool { return s.NextPage >= s.TotalPages
}

Чтобы это работало, нам нужно увеличивать NextPage каждый раз, когда отображается новая страница результатов. Этот метод проверяет, больше ли поле NextPage, чем поле TotalPages в экземпляре Search. Вот как это сделать:

func searchHandler(w http.ResponseWriter, r *http.Request) { // начало функции search.TotalPages = int(math.Ceil(float64(search.Results.TotalResults / pageSize))) // добавьте этот if блок if ok := !search.IsLastPage(); ok { search.NextPage++ } // остальная часть функции
}

Этот код должен быть помещен ниже .search-results в вашем файле index.html. Наконец, давайте добавим кнопку, которая позволит пользователю перейти на следующую страницу результатов.

<div class="pagination"> {{ if (ne .IsLastPage true) }} <a href="/search?q={{ .SearchKey }}&page={{ .NextPage }}" class="button next-page">Next</a> {{ end }}
</div>

Пока последняя страница для этого запроса не была достигнута, кнопка Next будет отображаться в нижней части списка результатов.

Как видите, href ссылки указывает на маршрут /search и сохраняет текущий поисковый запрос в параметре q, используя значение NextPage в параметре page.

Эту кнопку следует отображать только в том случае, если текущая страница больше 1. Давайте добавим кнопку Previous. Добавьте это ниже метода IsLastPage: Чтобы сделать это, нам нужно создать новый метод CurrentPage() в Search, чтобы реализовать это.

func (s *Search) CurrentPage() int { if s.NextPage == 1 { return s.NextPage } return s.NextPage - 1
}

Чтобы получить предыдущую страницу, просто вычтите 1 из текущей страницы. Текущая страница просто NextPage - 1, за исключением случаев, когда NextPage равен 1. Следующий метод делает именно это:

func (s *Search) PreviousPage() int { return s.CurrentPage() - 1
}

Измените элемент .pagination в вашем файле index.html следующим образом: Таким образом, мы можем добавить следующий код для отображения кнопки Previous, только если текущая страница больше 1.

<div class="pagination"> {{ if (gt .NextPage 2) }} <a href="/search?q={{ .SearchKey }}&page={{ .PreviousPage }}" class="button previous-page">Previous</a> {{ end }} {{ if (ne .IsLastPage true) }} <a href="/search?q={{ .SearchKey }}&page={{ .NextPage }}" class="button next-page">Next</a> {{ end }}
</div>

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

Показываем текущую страницу

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

Для этого нам нужно всего лишь изменить наш файл index.html следующим образом:

<div class="result-count"> {{ if (gt .Results.TotalResults 0)}} <p>About <strong>{{ .Results.TotalResults }}</strong> results were found. You are on page <strong>{{ .CurrentPage }}</strong> of <strong> {{ .TotalPages }}</strong>.</p> {{ else if (ne .SearchKey "") and (eq .Results.TotalResults 0) }} <p>No results found for your query: <strong>{{ .SearchKey }}</strong>.</p> {{ end }}
</div>

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

Browser showing current page

Деплоим на Heroku

Зарегистрируйте бесплатную учетную запись, затем перейдите по этой ссылке, чтобы создать новое приложение. Теперь, когда наше приложение полнофункционально, давайте продолжим и развернем его в Heroku. Я назвал приложение freshman-news. Укажите для приложения уникальное имя.

Затем выполните команду heroku login в терминале, чтобы войти в свою учетную запись Heroku. Затем следуйте инструкциям здесь, чтобы установить интерфейс командной строки Heroku на свой компьютер.

Если нет, запустите команду git init в корне каталога вашего проекта, а затем выполните команду ниже, чтобы установить heroku в качестве удаленного git-репозитория. Убедитесь, что вы инициализировали git-репозиторий для своего проекта. Замените freshman-news названием вашего приложения.

heroku git:remote -a freshman-news

Затем создайте Procfile в корневом каталоге вашего проекта (touch Procfile) и вставьте следующее содержимое:

web: bin/news-demo -apikey $NEWS_API_KEY

Создайте этот файл, если он еще не существует, в корне проекта. После этого укажите репозиторий GitHub для своего проекта и версию Go, которую вы используете, в своем файле go.mod, как показано ниже.

module github.com/freshman-tech/news-demo go 1.12.9

Нам нужно установить переменную среды NEWS_API_KEY, чтобы она могла быть передана в бинарный файл при запуске сервера. Перед развертыванием приложения перейдите на вкладку Settings на панели инструментов Heroku и нажмите Reveal Config Vars.

Heroku config variables

Наконец, сделайте коммит своего кода и сделайте пуш в Heroku с помощью следующих команд:

git add .
git commit -m "Initial commit"
git push heroku master

После завершения процесса деплоя вы можете открыть https://название_вашего_приложения.herokuapp.com, чтобы просмотреть и протестировать свой проект.

Заключение

Мы также изучили, как развернуть готовое приложение в Heroku. В этой статье мы успешно создали приложение News и обучились основам использования Go для веб-разработки.

Если у вас есть какие-либо вопросы относительно этого туториала, оставьте комментарий ниже, и я перезвоню вам. Я надеюсь, что эта статья была полезна для вас.

Спасибо за чтение!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть