Как отправить N получить запросы, где N> 10 URL - PullRequest
0 голосов
/ 10 февраля 2020

Я пытаюсь заставить N получать запросы, но мой код работает для 8 URL, но 10 всегда складывается без проблем.

Я новичок в GO, поэтому не могу понять проблему.

Я пытаюсь написать приложение, которое побьёт. NET приложение с той же задачей.

Не могли бы вы подсказать, что не так?

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    //"bufio"
    "time"
)

type HttpResponse struct {
    url      string
    response *http.Response
    err      error
}

func main() {
    fmt.Println("Hello, world3.")

    var urls []string = []string{
    "www.webmagnat.ro",
    "nickelfreesolutions.com",
    "scheepvaarttelefoongids.nl",
    "tursan.net",
    "plannersanonymous.com",
    "saltstack.com",
    "deconsquad.com",
    "migom.com",
    "tjprc.org",
    "worklife.dk",
    "food-hub.org"}

    start := time.Now()
    results := asyncHttpGets(urls)

    f, err := os.Create("test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }


    for _, result := range results {
        fmt.Printf("%s status: %s\n", result.url,
            result.response.Status)

            l, err := f.WriteString(result.url+"\n")
            if err != nil {
                fmt.Println(err)
                f.Close()
                return
            }
            _ = l
    }
    t := time.Now()
    elapsed := t.Sub(start)
    fmt.Printf("Ellipsed: %s\n", elapsed)

    err = f.Close()
    if err != nil {
        fmt.Println(err)
        return
    }


    fmt.Println("Buy, world2.")
}


func asyncHttpGets(urls []string) []*HttpResponse {
    ch := make(chan *HttpResponse, len(urls)) // buffered
    responses := []*HttpResponse{}
    for _, url := range urls {
         go func(url string) {
            fmt.Printf("Fetching %s \n", url)
            resp, err := http.Get("http://" + url)
            if err != nil {
                fmt.Printf("Failed to fetch %s\n", err)
                return
            }
            defer resp.Body.Close()

            if resp.StatusCode == http.StatusOK {
                fmt.Printf("HTTP Response Status : %v", resp.StatusCode)
                bodyBytes, err := ioutil.ReadAll(resp.Body)
                if err != nil {
                    log.Fatal(err)
                }
                bodyString := string(bodyBytes)

                fmt.Printf("HTTP Response Content Length : %v\n", len(bodyString))
            }

            ch <- &HttpResponse{url, resp, err}
        }(url)
    }

    for {
        select {
        case r := <-ch:
            fmt.Printf("%s was fetched\n", r.url)
            responses = append(responses, r)
            if len(responses) == len(urls) {
                return responses
            }
        case <-time.After(50 * time.Millisecond):
            fmt.Printf(".")
        }
    }

    return responses

}   

https://play.golang.org/p/pcKYYM_PgIX

Ответы [ 4 ]

1 голос
/ 10 февраля 2020

Проблема в том, что вы возвращаетесь в случае ошибки без записи в канал. Смотрите ваше if err != nil { return } заявление. Поскольку вы не пишете в канал, оператор len(responses) == len(urls) никогда не может быть истинным.

 go func(url string) {
            fmt.Printf("Fetching %s \n", url)
            resp, err := http.Get("http://" + url)
            if err != nil {
                fmt.Printf("Failed to fetch %s\n", err)
                return
            }
            defer resp.Body.Close()

            if resp.StatusCode == http.StatusOK {
                fmt.Printf("HTTP Response Status : %v", resp.StatusCode)
                bodyBytes, err := ioutil.ReadAll(resp.Body)
                if err != nil {
                    log.Fatal(err)
                }
                bodyString := string(bodyBytes)

                fmt.Printf("HTTP Response Content Length : %v\n", len(bodyString))
            }

            ch <- &HttpResponse{url, resp, err}
        }(url)
1 голос
/ 10 февраля 2020

Первая проблема заключается в том, что вы не возвращаете ответ в случае ошибки, поэтому len(responses) == len(urls), скорее всего, никогда не совпадет, что заставляет ваш l oop продолжать работать вечно.

Начните с добавления sync.WaitGroup для одновременных запросов

    var wg sync.WaitGroup

    ch := make(chan *HttpResponse) 
    responses := []*HttpResponse{}
    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()

Затем вы можете выбирать ответы до тех пор, пока не будут выполнены все невыполненные процедуры

    go func() {
        wg.Wait()
        close(ch)
    }()

    for r := range ch {
        fmt.Printf("%s was fetched\n", r.url)
        responses = append(responses, r)
    }

    return responses

Затем вам нужно решить, как обрабатывать ответы, вы собираетесь прочитать их в параллельном вызове, или вернуть их тела непрочитанными. Поскольку вы хотите, чтобы всегда пытался потреблять тела, если вы хотите повторно использовать соединение, и поскольку вы отложили Body.Close(), это в настоящее время должно происходить в рамках одного и того же вызова функции. Вы можете либо изменить тип httpResponse, чтобы сделать это возможным, либо заменить resp.Body буфером, содержащим ответ.

Наконец, вы захотите установить какое-то время ожидания для клиента (возможно, используя Context) и установить ограничение на число одновременных запросов.

0 голосов
/ 10 февраля 2020

Вы можете использовать следующую библиотеку:

Запросы : A Go библиотека для уменьшения головной боли при выполнении запросов HTTP (требование 20k / s)

https://github.com/alessiosavi/Requests

Он разработан для решения to many open files работы с параллельными запросами.

Идея состоит в том, чтобы выделить список запросов, чем отправьте их с настраиваемым «параллельным» фактором, который позволяет одновременно выполнять только «N» запрос.

Инициализировать запросы (у вас уже есть набор URL)

// This array will contains the list of request
var reqs []requests.Request

// N is the number of request to run in parallel, in order to avoid "TO MANY OPEN FILES. N have to be lower than ulimit threshold"
var N int = 12

// Create the list of request
for i := 0; i < 1000; i++ {
    // In this case, we init 1000 request with same URL,METHOD,BODY,HEADERS 
    req, err := requests.InitRequest("https://127.0.0.1:5000", "GET", nil, nil, true) 
    if err != nil {
        // Request is not compliant, and will not be add to the list
        log.Println("Skipping request [", i, "]. Error: ", err)
    } else {
        // If no error occurs, we can append the request created to the list of request that we need to send
        reqs = append(reqs, *req)
    }
}

На данный момент у нас есть список, содержащий запросы, которые должны быть отправлены. Давайте отправим их параллельно!

// This array will contains the response from the givens request
var response []datastructure.Response

// send the request using N request to send in parallel
response = requests.ParallelRequest(reqs, N)

// Print the response
for i := range response {
    // Dump is a method that print every information related to the response
    log.Println("Request [", i, "] -> ", response[i].Dump())
    // Or use the data present in the response
    log.Println("Headers: ", response[i].Headers)
    log.Println("Status code: ", response[i].StatusCode)
    log.Println("Time elapsed: ", response[i].Time)
    log.Println("Error: ", response[i].Error)
    log.Println("Body: ", string(response[i].Body))
}

Вы можете найти пример использования в папке example хранилища.

SPOILER :

0 голосов
/ 10 февраля 2020

Вы должны добавить конструкцию Wait Group, потому что, когда выполняются два потока, основной поток заканчивается. см .: https://yourbasic.org/golang/wait-for-goroutines-waitgroup/

func asyncHttpGets(urls []string) []*HttpResponse {
    ch := make(chan *HttpResponse, len(urls)) 
    var wg sync.WaitGroup
    responses := []*HttpResponse{}
    for _, url := range urls {
        go func(url string) {
            wg.Add(1)
            fmt.Printf("Fetching %s \n", url)
            resp, err := http.Get("http://" + url)
            if err != nil {
                fmt.Printf("Failed to fetch %s\n", err)
                return
            }
            defer resp.Body.Close()

            if resp.StatusCode == http.StatusOK {
                fmt.Printf("HTTP Response Status : %v", resp.StatusCode)
                bodyBytes, err := ioutil.ReadAll(resp.Body)
                if err != nil {
                    log.Fatal(err)
                }
                bodyString := string(bodyBytes)

                fmt.Printf("HTTP Response Content Length : %v\n", len(bodyString))
            }

            ch <- &HttpResponse{url, resp, err}
        }(url)
    }
    wg.Wait()
    for {
        select {
        case r := <-ch:
            fmt.Printf("%s was fetched\n", r.url)
            responses = append(responses, r)
            if len(responses) == len(urls) {
                return responses
            }
        case <-time.After(50 * time.Millisecond):
            fmt.Printf(".")
        }
    }
    return responses
}
...