Comment créer votre premiÚre application Web à l'aide de Go

Bonjour, Habr! Je vous présente la traduction de l'article "Comment construire votre premiÚre application web avec Go" par Ayooluwa Isaiah.


Ceci est le guide de votre premiÚre application Web Go. Nous allons créer une application de news qui utilise l' API News pour recevoir des articles de news sur un sujet spécifique et la déployer sur le serveur de production à la fin.


Vous pouvez trouver le code complet utilisé pour ce tutoriel dans ce référentiel GitHub .


Prérequis


La seule condition requise pour cette tùche est que Go soit installé sur votre ordinateur et que vous connaissiez un peu sa syntaxe et ses constructions. La version Go que j'ai utilisée pour créer l'application est également la plus récente au moment de la rédaction: 1.12.9 . Pour afficher la version installée de Go, utilisez la commande go version .


Si vous trouvez cette tùche trop difficile pour vous, allez à ma précédente leçon de langue d' introduction , qui devrait vous aider à démarrer.


Commençons donc!


Nous clonons le référentiel de fichiers de démarrage sur GitHub et cd dans le répertoire créé. Nous avons trois fichiers principaux: Dans le fichier main.go nous écrirons tout le code Go pour cette tùche. Le fichier index.html est le modÚle qui sera envoyé au navigateur et les de l'application se trouvent dans assets/styles.css .


Créer un serveur Web de base


Commençons par crĂ©er un serveur principal qui envoie le texte «Hello World!» Au navigateur lors de l'exĂ©cution d'une requĂȘte GET Ă  la racine du serveur. Modifiez votre fichier main.go pour qu'il ressemble Ă  ceci:


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

La premiÚre ligne du package main - déclare que le code du fichier main.go au package principal. AprÚs cela, nous avons importé le package net/http , qui fournit des implémentations client et serveur HTTP à utiliser dans notre application. Ce package fait partie de la bibliothÚque standard et est inclus avec chaque installation Go.


Dans la fonction main , http.NewServeMux() crĂ©e un nouveau multiplexeur de requĂȘtes HTTP et l'affecte Ă  la variable mux . Essentiellement, le multiplexeur de demandes fait correspondre l'URL des demandes entrantes Ă  la liste des chemins enregistrĂ©s et appelle le gestionnaire appropriĂ© pour le chemin chaque fois qu'une correspondance est trouvĂ©e.


Ensuite, nous enregistrons notre premiĂšre fonction de gestionnaire pour le chemin racine / . Cette fonction de gestionnaire est le deuxiĂšme argument de HandleFunc et possĂšde toujours la func (w http.ResponseWriter, r * http.Request) signature func (w http.ResponseWriter, r * http.Request) .


Si vous regardez la fonction indexHandler , vous verrez qu'elle a juste une telle signature, ce qui en fait un deuxiĂšme argument valide pour HandleFunc . Le paramĂštre w est la structure que nous utilisons pour envoyer des rĂ©ponses Ă  la requĂȘte HTTP. Il implĂ©mente la mĂ©thode Write() , qui prend une tranche d'octets et Ă©crit les donnĂ©es combinĂ©es dans le cadre de la rĂ©ponse HTTP.


En revanche, le paramĂštre r reprĂ©sente la requĂȘte HTTP reçue du client. C'est ainsi que nous accĂ©dons aux donnĂ©es envoyĂ©es par le navigateur Web sur le serveur. Nous ne l’utilisons pas encore ici, mais nous l’utiliserons certainement plus tard.


Enfin, nous avons la méthode http.ListenAndServe() , qui démarre le serveur sur le port 3000 si le port n'est pas défini par l'environnement. N'hésitez pas à utiliser un port différent si 3000 est utilisé sur votre ordinateur.


Ensuite, compilez et exécutez le code que vous venez d'écrire:


 go run main.go 

Si vous allez sur http: // localhost: 3000 dans votre navigateur, vous devriez voir le texte "Hello World!".


Courageux navigateur regardé le texte Hello World


Go Templates


Voyons les bases de la crĂ©ation de modĂšles dans Go. Si vous connaissez les modĂšles dans d'autres langues, cela devrait ĂȘtre assez facile Ă  comprendre.


Les modĂšles permettent de personnaliser facilement la sortie de votre application Web en fonction de l'itinĂ©raire sans avoir Ă  Ă©crire le mĂȘme code Ă  diffĂ©rents endroits. Par exemple, nous pouvons crĂ©er un modĂšle pour la barre de navigation et l'utiliser sur toutes les pages du site sans dupliquer le code. De plus, nous avons Ă©galement la possibilitĂ© d'ajouter une logique de base Ă  nos pages Web.


Go propose deux bibliothĂšques de modĂšles dans sa bibliothĂšque standard: text/template et html/template . Les deux fournissent la mĂȘme interface, mais le package html/template est utilisĂ© pour gĂ©nĂ©rer une sortie HTML protĂ©gĂ©e contre l'injection de code, nous allons donc l'utiliser ici.


Importez ce package dans votre fichier main.go et utilisez-le comme suit:


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

tpl est une variable de niveau package qui indique la définition d'un modÚle à partir des fichiers fournis. L'appel template.ParseFiles analyse le fichier index.html à la racine de notre répertoire de projet et vérifie sa validité.


Nous encapsulons l'appel template.ParseFiles dans template.Must afin que le code provoque une panique lorsqu'une erreur se produit. La raison pour laquelle nous paniquons ici au lieu d'essayer de gĂ©rer l'erreur est parce que cela n'a aucun sens de continuer Ă  exĂ©cuter le code si nous avons un modĂšle non valide. Il s'agit d'un problĂšme qui doit ĂȘtre rĂ©solu avant d'essayer de redĂ©marrer le serveur.


Dans la fonction indexHandler nous indexHandler le modĂšle crĂ©Ă© prĂ©cĂ©demment en fournissant deux arguments: oĂč nous voulons Ă©crire la sortie et les donnĂ©es que nous voulons transmettre au modĂšle.


Dans le cas ci-dessus, nous écrivons la sortie dans l'interface ResponseWriter et, puisque nous n'avons actuellement aucune donnée à transmettre à notre modÚle, nil est transmis comme deuxiÚme argument.


ArrĂȘtez le processus en cours dans votre terminal Ă  l'aide de Ctrl-C et redĂ©marrez-le avec go run main.go , puis actualisez votre navigateur. Vous devriez voir le texte «DĂ©mo de l'application News» sur la page, comme illustrĂ© ci-dessous:


Courageux navigateur affiché le texte de démonstration de l'application


Ajouter une barre de navigation Ă  la page


Remplacez le contenu de la <body> dans votre fichier index.html comme indiqué ci-dessous:


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

Redémarrez ensuite le serveur et actualisez votre navigateur. Vous devriez voir quelque chose de similaire à ceci:


Navigateur visualise une barre de navigation sans style


Travailler avec des fichiers statiques


Veuillez noter que la barre de navigation que nous avons ajoutée ci-dessus n'a pas de styles, malgré le fait que nous les ayons déjà spécifiés dans la <head> notre document.


En effet, le chemin / correspond à tous les chemins qui ne sont pas traités ailleurs. Par conséquent, si vous accédez à http: // localhost: 3000 / assets / style.css , vous obtiendrez toujours la page d'accueil de News Demo au lieu du fichier CSS car la route /assets/style.css pas été spécifiquement déclarée.


Mais la nécessité de déclarer des gestionnaires explicites pour tous nos fichiers statiques est irréaliste et ne peut pas évoluer. Heureusement, nous pouvons créer un gestionnaire pour servir toutes les ressources statiques.


La premiÚre chose à faire est de créer une instance de l'objet serveur de fichiers, en passant dans le répertoire dans lequel se trouvent tous nos fichiers statiques:


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

Ensuite, nous devons dire à notre routeur d'utiliser cet objet serveur de fichiers pour tous les chemins commençant par le préfixe /assets/ :


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

Maintenant tous ensemble:


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

Redémarrez le serveur et actualisez le navigateur. Les styles doivent s'activer comme indiqué ci-dessous:


Navigateur courageux afficher la barre de navigation de style



CrĂ©ons un itinĂ©raire qui gĂšre les requĂȘtes de recherche d'articles de presse. Nous utiliserons l' API News pour traiter les demandes, vous devez donc vous inscrire pour recevoir une clĂ© API gratuite ici .


Cette route attend deux paramĂštres de requĂȘte: q reprĂ©sente la requĂȘte de l'utilisateur et la page utilisĂ©e pour faire dĂ©filer les rĂ©sultats. Ce paramĂštre de page est facultatif. S'il n'est pas inclus dans l'URL, nous supposons simplement que le numĂ©ro de page des rĂ©sultats est dĂ©fini sur «1».


Ajoutez le gestionnaire suivant sous indexHandler Ă  votre fichier 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) } 

Le code ci-dessus extrait les paramĂštres q et page de l'URL de demande et les affiche tous les deux dans le terminal.


Enregistrez ensuite la fonction searchHandler tant que gestionnaire de chemin de /search , comme indiqué ci-dessous:


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

N'oubliez pas d'importer les packages fmt et net/url ci-dessus:


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

RedĂ©marrez maintenant le serveur, entrez la requĂȘte dans le champ de recherche et vĂ©rifiez le terminal. Vous devriez voir votre demande dans le terminal, comme indiquĂ© ci-dessous:




Créer un modÚle de données


Lorsque nous faisons une demande au point de terminaison News API/everything , nous attendons une réponse json au format suivant:


 { "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..." } ] } 

Pour travailler avec ces données dans Go, nous devons générer une structure qui reflÚte les données lors du décodage du corps de réponse. Bien sûr, vous pouvez le faire manuellement, mais je préfÚre utiliser le site Web JSON-to-Go , ce qui rend ce processus vraiment facile. Il génÚre une structure Go (avec des balises) qui fonctionnera pour ce JSON.


Tout ce que vous avez à faire est de copier l'objet JSON et de le coller dans le champ marqué JSON , puis de copier la sortie et de la coller dans votre code. Voici ce que nous obtenons pour l'objet JSON ci-dessus:


 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"` } 

Navigateur courageux afficher l'outil JSON to Go


J'ai apporté plusieurs modifications à la structure AutoGenerated en séparant le fragment Articles dans sa propre structure et en mettant à jour le nom de la structure. Collez la déclaration de variable tpl suivante dans main.go et ajoutez le package de time à votre importation:


 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"` } 

Comme vous le savez peut-ĂȘtre, Go requiert que tous les champs exportĂ©s de la structure commencent par une majuscule. Cependant, il est habituel de reprĂ©senter les champs JSON en utilisant camelCase ou snake_case , qui ne commencent pas par une majuscule.


Par conséquent, nous utilisons des balises de champ de structure telles que json:"id" pour afficher explicitement le champ de structure dans le champ JSON, comme indiqué ci-dessus. Il vous permet également d'utiliser des noms complÚtement différents pour le champ de structure et le champ json correspondant, si nécessaire.


Enfin, crĂ©ons un type de structure diffĂ©rent pour chaque requĂȘte de recherche. Ajoutez ceci sous la structure des Results dans main.go :


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

Cette structure reprĂ©sente chaque requĂȘte de recherche effectuĂ©e par l'utilisateur. SearchKey est la requĂȘte elle-mĂȘme, le champ NextPage permet de faire dĂ©filer les rĂ©sultats, TotalPages - le nombre total de pages de rĂ©sultats de requĂȘte et Results - la page actuelle des rĂ©sultats de requĂȘte.


Envoyer une demande à l'aide de l'API News et afficher les résultats


Maintenant que nous avons le modÚle de données pour notre application, continuons et faisons des demandes à l'API News, puis restituons les résultats sur la page.


Étant donnĂ© que l'API News nĂ©cessite une clĂ© API, nous devons trouver un moyen de la transmettre dans notre application sans codage en dur dans le code. Les variables d'environnement sont une approche courante, mais j'ai dĂ©cidĂ© d'utiliser Ă  la place des drapeaux de ligne de commande. Go fournit un package d' flag qui prend en charge l'analyse de base des indicateurs de ligne de commande, et c'est ce que nous allons utiliser ici.


apiKey abord une nouvelle variable apiKey sous la variable tpl :


 var apiKey *string 

Ensuite, utilisez-le dans la fonction main comme suit:


 func main() { apiKey = flag.String("apikey", "", "Newsapi.org access key") flag.Parse() if *apiKey == "" { log.Fatal("apiKey must be set") } //    } 

Ici, nous appelons la méthode flag.String() , qui nous permet de définir un indicateur de chaßne. Le premier argument de cette méthode est le nom de l'indicateur, le second est la valeur par défaut et le troisiÚme est la description de l'utilisation.


AprÚs avoir défini tous les indicateurs, vous devez appeler flag.Parse() pour les flag.Parse() réellement. Enfin, comme l' apikey est un composant requis pour cette application, nous nous assurons que le programme se bloque si ce drapeau n'est pas défini pendant l'exécution du programme.


Assurez-vous d'ajouter le package d' flag à votre importation, puis redémarrez le serveur et transmettez l'indicateur d' apikey requis, comme indiqué ci-dessous:


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

Ensuite, continuons et mettons Ă  jour searchHandler afin que la requĂȘte de recherche de l'utilisateur soit envoyĂ©e Ă  newsapi.org et que les rĂ©sultats soient affichĂ©s dans notre modĂšle.


Remplacez les deux appels à la méthode fmt.Println() à la fin de la fonction searchHandler code suivant:


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

Tout d'abord, nous créons une nouvelle instance de la structure de Search et définissons la valeur du champ SearchKey sur la valeur du paramÚtre d'URL q dans la demande HTTP.


AprÚs cela, nous convertissons la variable de page en un entier et NextPage le résultat au champ NextPage variable de search . Ensuite, nous créons la variable pageSize et définissons sa valeur sur 20. Cette variable pageSize représente le nombre de résultats que l'API news renverra dans sa réponse. Cette valeur peut aller de 0 à 100.


Ensuite, nous créons le point de terminaison à l'aide de fmt.Sprintf() et lui faisons une demande GET. Si la réponse de l'API News n'est pas 200 OK , nous renverrons une erreur générale du serveur au client. Sinon, le corps de la réponse est analysé dans search.Results .


Ensuite, nous calculons le nombre total de pages en divisant le champ pageSize par pageSize . Par exemple, si une requĂȘte renvoie 100 rĂ©sultats et que nous n'en affichons que 20 Ă  la fois, nous devrons faire dĂ©filer cinq pages pour voir les 100 rĂ©sultats de cette requĂȘte.


AprÚs cela, nous rendons notre modÚle et passons la variable de search comme interface de données. Cela nous permet d'accéder aux données d'un objet JSON dans notre modÚle, comme vous le verrez.


Avant de passer à index.html , assurez-vous de mettre à jour vos importations comme indiqué ci-dessous:


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

Continuons et affichons les résultats sur la page en modifiant le fichier index.html comme suit. Ajoutez ceci sous la <header> :


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

Pour accéder au champ de structure dans le modÚle, nous utilisons l'opérateur point. Cet opérateur fait référence à un objet de structure (dans ce cas, la search ), puis à l'intérieur du modÚle, nous {{.Results}} simplement le nom du champ (comme {{.Results}} ).


Le bloc de range nous permet d'itérer sur une tranche dans Go et de produire du HTML pour chaque élément de la tranche. Ici, nous parcourons la tranche des structures Article contenues dans le champ Articles et affichons le HTML à chaque itération.


Redémarrez le serveur, actualisez le navigateur et recherchez des actualités sur un sujet populaire. Vous devriez obtenir une liste de 20 résultats par page, comme indiqué dans la capture d'écran ci-dessous.


Navigateur visualise les listes de nouvelles


Enregistrer la requĂȘte de recherche en Ă©tranger


Notez que la requĂȘte de recherche disparaĂźt de l'entrĂ©e lorsque la page est actualisĂ©e avec les rĂ©sultats. IdĂ©alement, la requĂȘte doit ĂȘtre conservĂ©e jusqu'Ă  ce que l'utilisateur effectue une nouvelle recherche. Voici comment fonctionne la recherche Google, par exemple.


Nous pouvons facilement résoudre ce problÚme en mettant à jour l'attribut value de la balise d' input dans notre fichier index.html comme suit:


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

RedĂ©marrez votre navigateur et effectuez une nouvelle recherche. La requĂȘte de recherche sera enregistrĂ©e comme indiquĂ© ci-dessous:



Format date de publication


Si vous regardez la date dans chaque article, vous verrez qu'elle est mal lisible. La sortie actuelle montre comment l'API News renvoie la date de publication de l'article. Mais nous pouvons facilement changer cela en ajoutant une méthode à la structure Article et en l'utilisant pour formater la date au lieu d'utiliser la valeur par défaut.


Ajoutons le code suivant juste en dessous de la structure Article dans main.go :


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

Ici, une nouvelle méthode FormatPublishedDate créée dans la structure Article , et cette méthode formate le champ PublishedAt dans Article et renvoie une chaßne au format suivant: 10 2009 .


Pour utiliser cette nouvelle mĂ©thode dans votre modĂšle, remplacez .PublishedAt par .FormatPublishedDate dans votre fichier index.html . RedĂ©marrez ensuite le serveur et rĂ©pĂ©tez la requĂȘte de recherche prĂ©cĂ©dente. Cela produira les rĂ©sultats avec une heure correctement formatĂ©e, comme indiquĂ© ci-dessous:


Courageux navigateur affichés une date correctement formatée


Affiche le nombre total de résultats.


AmĂ©liorons l'interface utilisateur de notre application d'actualitĂ©s en indiquant le nombre total de rĂ©sultats en haut de la page, puis affichons un message au cas oĂč aucun rĂ©sultat n'aurait Ă©tĂ© trouvĂ© pour une requĂȘte particuliĂšre.


Tout ce que vous avez à faire est d'ajouter le code suivant en tant qu'enfant du .container , juste au-dessus de l'élément .search-results dans votre fichier 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> 

Go , . gt , , TotalResults Results . , .


, SearchKey ( (ne .SearchKey "") ) TotalResults ( (eq .Results.TotalResults 0) ), «No results found».


, . «No results found».


Navigateur ne montrant aucun résultat trouvé message


. , :


Navigateur affichant le nombre de résultats en haut de la page



20 , , .


Next , . , , Search main.go :


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

, NextPage , TotalPages Search . , NextPage , . :


 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 .


Previous . , 1. , CurrentPage() Search , . IsLastPage :


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

NextPage - 1 , , NextPage 1. , 1 . :


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

, Previous , 1. .pagination index.html :


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

, , .


Navigateur affichant la page actuelle


Heroku


, , Heroku. , , . . freshman-news .


, Heroku . heroku login , Heroku.


, git- . , git init , , heroku 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 

Settings Heroku Reveal Config Vars . NEWS_API_KEY , .


Variables de configuration Heroku


, Heroku :


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

https://__.herokuapp.com , .


Conclusion


News Go -. , Heroku.


, . - , , .


Merci d'avoir lu!

Source: https://habr.com/ru/post/fr475390/


All Articles