Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Go’s net/http package covers both sides of HTTP: client code creates requests and reads responses, while server code receives requests through an http.Handler and writes responses. Start with http.Get for a basic fetch; use http.NewRequestWithContext and a reusable http.Client when you need headers, methods, bodies, cancellation, redirects, or connection policy. On the server, register a handler on a mux and serve it with http.Server.

This guide follows the current standard-library documentation (accessed September 29, 2026). Check the package documentation for behavior specific to the Go version your project supports.

Make a simple HTTP request

For a one-off GET where the defaults are acceptable, the shortest form is:

resp, err := http.Get("https://example.com")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
    log.Fatal(err)
}
fmt.Println(resp.StatusCode, len(body))

http.Get returns an error only when the request cannot be made or the response cannot be obtained. A server response such as 404 or 500 is still a successful transport operation, so inspect resp.StatusCode yourself. Always close resp.Body when finished; leaving it open can prevent persistent connections from being reused.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a request, context, and reusable client

Construct a request when you need a method other than GET, custom headers, a body, cancellation, or a deadline. Reuse clients: http.Client and its transports are safe for concurrent use, and transports cache connections.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    client := &http.Client{
        Timeout: 10 * time.Second,
    }

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(
        ctx,
        http.MethodGet,
        "https://api.example.com/items",
        nil,
    )
    if err != nil {
        panic(err)
    }
    req.Header.Set("Accept", "application/json")

    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        data, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
        panic(fmt.Sprintf("unexpected status %s: %s", resp.Status, data))
    }

    body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
    if err != nil {
        panic(err)
    }
    fmt.Println(string(body))
}

The context governs connection acquisition, request transmission, and reading response headers and the body. A client timeout provides an additional upper bound. Use an explicit response-size limit when consuming untrusted or unexpectedly large data, then decode the bytes as JSON or another format.

Send methods, bodies, and headers

payload := strings.NewReader(`{"name":"Ada"}`)
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
    "https://api.example.com/items", payload)
if err != nil {
    return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := client.Do(req)

Use io.Reader for request bodies. Close the response body even when you intend to reject its status. If the request carries credentials, review redirect behavior: Go’s security guidance describes stripping sensitive headers on cross-domain redirects as defense in depth, but your application should still decide which destinations are trusted. See Go’s security decisions.

Understand Client, Transport, and connection reuse

Use the client for higher-level policy such as redirects, cookies, and an overall timeout. Use a transport for lower-level networking: proxies, TLS, compression, keep-alives, and connection limits.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
transport := &http.Transport{
    MaxIdleConns:        100,
    MaxIdleConnsPerHost: 20,
    IdleConnTimeout:     90 * time.Second,
}
client := &http.Client{Transport: transport}

Choose limits for your workload rather than copying universal numbers. Call client.CloseIdleConnections() when a long-running process has a deliberate reason to release idle sockets. The default transport supports HTTP/2 in documented HTTPS cases. A custom transport does not automatically enable every default protocol behavior, so check the documentation for your target Go release before configuring HTTP/2 or newer protocol fields.

Write an HTTP server

An http.Handler receives an http.ResponseWriter and *http.Request. Register handlers with a mux, then serve the mux.

package main

import (
    "fmt"
    "html"
    "log"
    "net/http"
    "time"
)

func home(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    fmt.Fprintf(w, "hello from %sn", html.EscapeString(r.URL.Path))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", home)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }

    log.Printf("listening on %s", server.Addr)
    if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatal(err)
    }
}

Run it with go run ., then open http://localhost:8080/. The listening method normally returns only on an error, so handle that return value instead of discarding it. A minimal alternative is http.HandleFunc("/", home) followed by http.ListenAndServe(":8080", nil); an explicit server makes timeout and header controls visible.

Read requests and write responses safely

Treat paths, query parameters, headers, and bodies as untrusted. Validate methods and input sizes, and escape data before placing it in HTML. Check r.Host when the application should serve only particular hostnames; the request documentation warns that handlers must validate whether a Host value is authoritative. Host-specific mux patterns can help protect registered routes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
func create(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        w.Header().Set("Allow", http.MethodPost)
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
    defer r.Body.Close()
    // Decode and validate the bounded body here.
    w.WriteHeader(http.StatusCreated)
}

Incoming request contexts are canceled when the client connection closes, when an HTTP/2 request is canceled, or when the handler returns. Pass r.Context() to database and outbound HTTP calls so work stops with the request.

Choose timeouts and cancellation deliberately

  • Outgoing calls: use context.WithTimeout or WithDeadline for per-operation limits, plus a client policy appropriate to the service.
  • Incoming calls: configure ReadTimeout, WriteTimeout, and header limits on http.Server. Values depend on payload size, latency, and streaming requirements.
  • Streaming: avoid a write timeout that is shorter than the intended stream, or design a separate server policy for streaming endpoints.

Testing handlers with net/http/httptest

The net/http/httptest package lets tests exercise handlers without a live external service. httptest.NewRequest creates a request intended for a server handler, while httptest.NewRecorder captures the response.

func TestHome(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
    rec := httptest.NewRecorder()

    home(rec, req)

    if rec.Code != http.StatusOK {
        t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
    }
    if got := rec.Body.String(); got == "" {
        t.Fatal("empty response")
    }
}

For integration-style client tests, httptest.NewServer(handler) starts a local HTTP server; close it with defer server.Close(), then send your real client request to server.URL. Consult the current httptest documentation for available helpers.

Or skip the browser setup

If your Go service needs website images or PDFs rather than an API response, ScreenshotNeo provides a website screenshot API and MCP server. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing result. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

One GET request returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Features include full-page and CSS-selector captures, lazy-image loading, dark mode, device presets, custom viewports and retina scale, PDF paper and page controls, HTML/CSS rendering, custom JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Existing parameter names from other screenshot APIs also work. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000, and every feature is available on every plan. Sign up free.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common failures

“It returned 404, but err is nil”

That is expected client behavior. Inspect resp.StatusCode and handle non-2xx responses as application errors.

Connections are not reused or file descriptors grow

Ensure every response body is closed, preferably immediately after checking the error. Reuse one client and transport instead of creating them per request.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Requests hang indefinitely

Add a request context deadline and a client timeout. On servers, set read and write limits appropriate to the endpoint and investigate slow upstream dependencies.

Credentials appear at an unexpected redirect target

Review redirect policy and trusted domains. Do not rely solely on default header stripping; prevent redirects to destinations your application does not authorize.

Custom transport changed protocol behavior

Compare it with the default transport and check the Go version’s documentation for HTTP/2 and other protocol configuration. Reproduce the required settings explicitly.

HTML contains escaped or unsafe data

Escape output in HTML contexts and validate paths, hosts, methods, and body sizes before processing.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does net/http treat every non-2xx response as an error?

No. Client transport errors are returned through err; an HTTP status such as 404 or 500 is returned in resp and must be checked by your code.

Are http.Client and http.Transport safe to share?

Yes. They are designed for concurrent use. Reusing them also allows transports to reuse connections.

When should I use http.Get instead of NewRequestWithContext?

Use http.Get for a simple GET with default policy. Construct a request for custom methods, headers, bodies, cancellation, deadlines, or explicit client behavior.

What package should I use to test a handler?

Use net/http/httptest, commonly with NewRequest and NewRecorder for a direct handler test or NewServer for an end-to-end client test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.