有无阻塞的http客户端吗?

时间:2019-12-20 07:41:42

标签: go

许多语言都有自己的高级非阻塞HTTP客户端,例如python的aiohttp。即,它们发出HTTP请求;不要等待回应;当响应到达时,它们会进行某种回调。

我的问题是

  1. 有一个Go软件包吗?
  2. 或者我们只是创建一个使用常规HTTP客户端的goroutine?
  3. 哪种方法更好?

3 个答案:

答案 0 :(得分:2)

其他语言具有这样的功能,因为当它们阻塞等待的请求时,它们就会阻塞正在使用的线程。 Java,Python或NodeJS就是这种情况。因此,为了使它们有用,开发人员需要使用回调实现这种长期存在的阻塞操作。造成这种情况的根本原因是使用了C库,该C库阻止了输入输出操作中的线程。

Go不使用C库(仅在某些情况下可以使用,但可以将其关闭),并自行进行系统调用。在执行此操作时,执行当前goroutine的线程将其停放并执行另一个goroutine。因此,您可以拥有大量被阻塞的goroutine,而不会耗尽线程。就内存而言,Goroutine很便宜,线程是操作系统实体。

在Go中使用goroutines更好。由于上述原因,无需创建异步客户端。

要在Java中进行比较,您很快就会遇到多个线程。下一步将是合并它们,因为它们很昂贵。池化意味着限制并发性。

答案 1 :(得分:1)

正如其他人所说,goroutines是行之有效的方法(双关语意)。

最小示例:

type nonBlocking struct {
    Response *http.Response
    Error    error
}

const numRequests = 2

func main() {

    nb := make(chan nonBlocking, numRequests)
    wg := &sync.WaitGroup{}

    for i := 0; i < numRequests; i++ {
        wg.Add(1)
        go Request(nb)
    }

    go HandleResponse(nb, wg)

    wg.Wait()

}

func Request(nb chan nonBlocking) {

    resp, err := http.Get("http://example.com")
    nb <- nonBlocking{
        Response: resp,
        Error:    err,
    }
}

func HandleResponse(nb chan nonBlocking, wg *sync.WaitGroup) {

    for get := range nb {

        if get.Error != nil {
            log.Println(get.Error)
        } else {
            log.Println(get.Response.Status)
        }
        wg.Done()
    }

}

答案 2 :(得分:0)

Yip,内置于标准库中,只是开箱即用的简单函数调用无法使用。

以这个例子

package main

import (
    "flag"
    "log"
    "net/http"
    "sync"
    "time"
)

var url string
var timeout time.Duration

func init() {
    flag.StringVar(&url, "url", "http://www.stackoverflow.com", "url to GET")
    flag.DurationVar(&timeout, "timeout", 5*time.Second, "timeout for the GET operation")
}

func main() {
    flag.Parse()

    // We use the channel as our means to
    // hand the response over
    rc := make(chan *http.Response)

    // We need a waitgroup because all goroutines exit when main exits
    var wg sync.WaitGroup

    // We are spinning up an async request
    // Increment the counter for our WaitGroup.
    // What we are basically doing here is to tell the WaitGroup
    // "Hey, there is one more task you have to wait for!"
    wg.Add(1)

    go func() {
        // Notify the WaitGroup that one task is done as soon
        // as we exit the goroutine.
        defer wg.Done()

        log.Printf("Doing GET request on \"%s\"", url)
        resp, err := http.Get(url)

        if err != nil {
            log.Printf("GET for %s: %s", url, err)
        }

        // We send the reponse downstream
        rc <- resp

        // Now, the goroutine exits, the defered call to wg.Done()
        // is executed.
    }()

    // And here we do our async processing.
    // Note that you could have done the processing in the first goroutine
    // as well, since http.Get would be a blocking operation and any subsequent
    // code in the goroutine would have been excuted only after the Get returned.
    // However, I put te processing into its own goroutine for demonstration purposes.
    wg.Add(1)
    go func() {

        // As above
        defer wg.Done()

        log.Println("Doing something else")

        // Setting up a timer for a timeout.
        // Note that this could be done using a request with a context, as well.
        to := time.NewTimer(timeout).C

        select {
        case <-to:
            log.Println("Timeout reached")
            // Exiting the goroutine, the deferred call to wg.Done is executed
            return
        case r := <-rc:
            if r == nil {
                log.Printf("Got no useful response from GETting \"%s\"", url)
                // Exiting the goroutine, the deferred call to wg.Done is executed
                return
            }
            log.Printf("Got response with status code %d (%s)", r.StatusCode, r.Status)
            log.Printf("Now I can do something useful with the response")

        }
    }()

    // Now we have set up all of our tasks,
    // we are waiting until all of them are done...
    wg.Wait()
    log.Println("All tasks done, exiting")
}

如果您仔细观察一下,我们将提供所有构建块,以使GET网址和异步处理响应成为可能。我们可以开始对此进行抽象:

package main

import (
    "flag"
    "log"
    "net/http"
    "time"
)

var url string
var timeout time.Duration

func init() {
    flag.StringVar(&url, "url", "http://www.stackoverflow.com", "url to GET")
    flag.DurationVar(&timeout, "timeout", 5*time.Second, "timeout for the GET operation")
}

type callbackFunc func(*http.Response, error) error

func getWithCallBack(u string, callback callbackFunc) chan error {

    // We create a channel which we can use to notify the caller of the
    // result of the callback.
    c := make(chan error)
    go func() {
        c <- callback(http.Get(u))
    }()
    return c
}

func main() {
    flag.Parse()

    c := getWithCallBack(url, func(resp *http.Response, err error) error {

        if err != nil {
            // Doing something useful with the err.
            // Add additional cases as needed.
            switch err {
            case http.ErrNotSupported:
                log.Printf("GET not supported for \"%s\"", url)
            }
            return err
        }
        log.Printf("GETting \"%s\": Got response with status code %d (%s)", url, resp.StatusCode, resp.Status)
        return nil
    })

    if err := <-c; err != nil {
        log.Printf("Error GETting \"%s\": %s", url, err)
    }
    log.Println("All tasks done, exiting")
}

然后您去(双关语意):GET请求的异步处理。