2 votes

Golang: Faire un appel HTTP et analyser JSON avec Go Routines et JSON

Je suis relativement nouveau en golang, et je voulais créer un moyen d'appeler simultanément plusieurs URL et de parser les documents JSON. Cependant, je ne suis pas sûr si j'utilise correctement les go routines et les channels. À ce stade, je ne sais pas si je ne "pense pas en Go" correctement ou si ma compréhension/approche des go routines et des channels n'est pas précise.

De plus, lors du parsing, je souhaite parser la propriété results du corps, qui est un tableau, et chaque élément dans results contient une propriété doc que je souhaite filtrer.

L'objectif est d'effectuer plusieurs fetches, simultanément et de parser les réponses pour obtenir uniquement la propriété doc à l'intérieur d'un tableau de résultats de corps.

J'apprécierais certainement tout aperçu ou suggestion pour mieux comprendre les choses. Merci d'avance.

package operations

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

// CouchbaseDoc analyse la propriété .doc des documents sync gateway
type CouchbaseDoc struct {
    Doc map[string]string `json:"doc"`
}

// Results décompose... results est une propriété de body, et est un tableau d'objets
type Results struct {
    Results []byte `json:"results"`
}

func createURLs(channels []string) map[string]string {
    urlMap := make(map[string]string)

    domain := "swap" + strings.TrimSpace(os.Getenv("env"))
    bucket := strings.TrimSpace(os.Getenv("bucket"))
    for _, doctype := range channels {
        urlMap[doctype] = fmt.Sprintf("https://%s.endpoint.com/%s/_changes?filter=sync_gateway/bychannel&channels=%s&include_docs=true", domain, bucket, doctype)
    }

    return urlMap
}

func getChangesFeed(url string, ch chan map[string]string) {
    resp, _ := http.Get(url)

    body, _ := ioutil.ReadAll(resp.Body)

    go parseBody(body, ch)
}

func parseBody(body []byte, ch chan map[string]string) {
    var results Results
    var doc CouchbaseDoc
    json.Unmarshal(body, &results)
    json.Unmarshal(results.Results, &doc)
    // écrire vers les réponses
    ch <- doc.Doc
}

func fetchDocs(channels []string) {
    urls := createURLs(channels)

    // Le channel de réponse est l'endroit où toutes les go routines feront le travail
    responses := make(chan map[string]string)
    for _, url := range urls {
        go getChangesFeed(url, responses)
    }

    // Lire du channel des réponses
    docs := <-responses
    for doc := range docs {
        fmt.Println(doc) // Cela devrait afficher les résultats ??
    }
}

4voto

jrefior Points 2068

La correction

Cette ligne :

docs := <-responses

Ne recevra qu'un élément du canal, pas tous les éléments. Cependant, vous pourriez appeler l'opération de réception une fois par envoi attendu sur le canal, ce serait la solution la plus simple pour votre code :

responses := make(chan map[string]string)
for _, url := range urls {
    go getChangesFeed(url, responses)
}

for x := 0; x < len(urls); x++ {
    fmt.Println(<-responses)
}

Plus d'informations

Remarquez que vous utilisez un canal non mis en mémoire tampon car vous n'avez pas donné de longueur au canal. Une boucle for e := range ch { est seulement appropriée pour un canal mis en mémoire tampon, et seulement après que le canal mis en mémoire tampon a été fermé.

Fermer un canal mis en mémoire tampon indique qu'aucune donnée supplémentaire ne sera envoyée sur le canal, et ne correspondrait probablement pas bien à la conception de votre programme (surtout sans un sync.WaitGroup).

Utiliser un canal mis en mémoire tampon est donc correct : vous devez juste savoir que ni l'envoi ni la réception ne se feront à moins que les deux côtés soient prêts : ce qui signifie que chacun est bloqué et en attente de l'autre.

Cela a été facilement accompli avec le code ci-dessus en mettant les envois dans des goroutines et en mettant en file d'attente un nombre égal d'opérations de réception dans la goroutine principale en utilisant une boucle avec un compteur égal.

Pour en savoir plus, consultez The Language Specification et Effective Go ainsi que les sections Mutex et WaitGroup de la documentation du package sync.

Démonstration exécutable

Voici un exemple complet et exécutable qui illustre le principe :

package main

import(
    "fmt"
    "time"
)

func Sleep1(ch chan int) {
    time.Sleep(time.Second)
    ch <- 1
}

func Sleep3(ch chan int) {
    time.Sleep(time.Second * 3)
    ch <- 3
}

func Sleep5(ch chan int) {
    time.Sleep(time.Second * 5)
    ch <- 5
}

func main() {
    ch := make(chan int)
    go Sleep1(ch)
    go Sleep3(ch)
    go Sleep5(ch)
    for x := 0; x < 3; x++ {
        fmt.Println(<-ch)
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X