SERP API 4 min read

Bound Go API Concurrency, Retries and Cancellation

A Go API client needs to bound simultaneous requests, retry attempts and caller deadlines independently.

(Updated: ) • 613 words

A Go API client needs to bound simultaneous requests, retry attempts and caller deadlines independently. Waiting for a work slot must remain cancellable, and a retry must release response resources before another request. Account concurrency is not a fixed requests-per-second guarantee.

A complete local-contract client example

package fixtures

import (
    "bytes"
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "net/http"
    "time"
)

type Organic struct {
    Position int `json:"position"`
    Title string `json:"title"`
    Link string `json:"link"`
}
type Response struct {
    Code int `json:"code"`
    Data struct { Organic []Organic `json:"organic"` } `json:"data"`
}

// Search uses caller cancellation, bounded attempts and one acquired lane.
// A fixed delay is used for the fixture; production should respect Retry-After.
func Search(ctx context.Context, client *http.Client, endpoint, key, query string,
            lanes chan struct{}, maxAttempts int) ([]Organic, error) {
    if maxAttempts < 1 { return nil, errors.New("positive retry budget required") }
    select {
    case lanes <- struct{}{}: defer func() { <-lanes }()
    case <-ctx.Done(): return nil, ctx.Err()
    }
    body, err := json.Marshal(map[string]any{"t":"google", "s":query, "country":"us", "language":"en"})
    if err != nil { return nil, err }
    for attempt := 0; attempt < maxAttempts; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
        if err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer " + key)
        req.Header.Set("Content-Type", "application/json")
        resp, err := client.Do(req)
        if err != nil { return nil, err }
        retry := resp.StatusCode == 429 || resp.StatusCode >= 500
        if retry {
            io.Copy(io.Discard, io.LimitReader(resp.Body, 1<<20))
            resp.Body.Close()
            if attempt + 1 == maxAttempts { return nil, fmt.Errorf("retry budget exhausted: HTTP %d", resp.StatusCode) }
            select {
            case <-time.After(10 * time.Millisecond): continue
            case <-ctx.Done(): return nil, ctx.Err()
            }
        }
        defer resp.Body.Close()
        if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) }
        var parsed Response
        if err := json.NewDecoder(io.LimitReader(resp.Body, 4<<20)).Decode(&parsed); err != nil { return nil, err }
        if parsed.Code != 0 { return nil, fmt.Errorf("business code %d", parsed.Code) }
        return parsed.Data.Organic, nil
    }
    return nil, errors.New("unreachable")
}

This client was exercised with local httptest servers using Go 1.27.2. The tests cover nested organic parsing, a transient 429 followed by success, a non-retried authentication failure, cancellation while waiting for capacity and peak concurrency no greater than three. No live-provider throughput test was performed.

Replace fixture timing before production use

The example uses a short fixed delay to make its local test deterministic. A production policy must respect Retry-After when supplied, apply a bounded backoff/jitter policy and keep the delay within the caller deadline. Decide which business errors are retryable from the actual documented contract.

Create the lane channel from the account capacity and application limit. A larger channel does not create additional account lanes. Preserve attempt count and final failure status for the caller.

Know what the example does not implement

Use the separate Go JSON guide for the response struct and the enterprise failure-budget guide for system-level fallback decisions.

  • It is not a distributed account scheduler across multiple processes.
  • It does not classify every provider-specific business error.
  • It does not measure live latency or promise a throughput level.
  • Its tests do not evaluate SERP relevance or generated-answer quality.

Reference documentation: pkg.go.dev — /net/http; pkg.go.dev — /context; www.searchcans.com — /google-search-api.

Tags:

SERP API Tutorial API Development Integration Web Scraping
SearchCans Team

SearchCans Team

SERP API & Reader API Experts

The SearchCans engineering team builds high-performance search APIs serving developers worldwide. We share practical tutorials, best practices, and insights on SERP data, web scraping, RAG pipelines, and AI integration.

Ready to build with SearchCans?

Test SERP API and Reader API with 100 free credits. No credit card required.