Beispiel #1
0
// send sends a request, kills it after passed timeout and writes the result to passed channel
func send(httpClient *http.Client, tr *http.Transport, req request, timeout time.Duration, resp chan request) {
	if *debug {
		fmt.Println("Sending a request to", req.httpReq.URL)
	}
	// Send the HTTP request in it's own goroutine using a HTTP client to be able to abort the request if reached timeout
	go func() {
		start := time.Now()
		resp, err := httpClient.Do(req.httpReq)
		if err != nil {
			req.err = err
		} else {
			req.StatusCode = resp.StatusCode
			req.Duration = time.Since(start)
			resp.Body.Close()
		}
		// Indicate the request is finished
		close(req.done)
	}()
	// Either the request finished, or the timeout triggers
	select {
	case <-req.done:
		// Request is done, please continue
	case <-time.After(timeout):
		req.Duration = timeout
		// Manually cancel the request in flight
		tr.CancelRequest(req.httpReq)
		req.err = errTimeout
	}
	// Send back on the response channel
	resp <- req
}
Beispiel #2
0
func Fanout(transport *http.Transport, request *http.Request, endpoints []*Endpoint) (*http.Response, error) {
	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		return nil, err
	}

	responses := make(chan *http.Response, len(endpoints))
	errs := make(chan error, len(endpoints))

	for _, e := range endpoints {
		url := *request.URL
		url.Scheme = "http"
		url.Host = e.Addr

		req := *request
		req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
		req.URL = &url
		req.Close = true

		request := &req

		go func() {
			response, err := transport.RoundTrip(request)

			if err != nil {
				errs <- err
			} else {
				responses <- response
			}
		}()

		defer transport.CancelRequest(request)
	}

	var fanoutErr error
	var response *http.Response

	for i := 0; i < len(endpoints); i++ {
		select {
		case fanoutErr = <-errs:
		case response = <-responses:
			if response.StatusCode < 400 {
				return response, nil
			}
		}
	}

	if response != nil {
		return response, nil
	}

	return nil, fanoutErr
}
Beispiel #3
0
// HTTPDo runs the HTTP request in a goroutine and passes the response to f in
// that same goroutine. After the goroutine finishes, returns the result of f.
// If ctx.Done() receives before f returns, calls transport.CancelRequest(req) before
// before returning. Even though HTTPDo executes f in another goroutine, you
// can treat it as a synchronous call to f. Also, since HTTPDo uses client to run
// requests and transport to cancel them, client.Transport should be transport
// in most cases.
//
// Example Usage:
//  type Resp struct { Num int `json:"num"` }
//  var resp *Resp
//  err := HttpDo(ctx, client, transport, req, func(resp *http.Response, err error) error {
//    if err != nil { return err }
//    defer resp.Body.Close()
//
//    if err := json.NewDecoder(resp.Body).Decode(resp); err != nil {
//      return err
//    }
//    return nil
//  })
//  if err != nil { return err }
//  // do something with resp...
//
// This func was stolen/adapted from https://blog.golang.org/context
func HTTPDo(ctx context.Context, client *http.Client, transport *http.Transport, req *http.Request, f func(*http.Response, error) error) error {
	// Run the HTTP request in a goroutine and pass the response to f.
	c := make(chan error, 1)

	// see if the ctx was already cancelled/timed out
	select {
	case <-ctx.Done():
		return ErrCancelled
	default:
	}

	go func() {
		c <- f(client.Do(req))
	}()

	select {
	case <-ctx.Done():
		transport.CancelRequest(req)
		<-c // Wait for f to return.
		return ctx.Err()
	case err := <-c:
		return err
	}
}