New to go. This is currently my code:
client := http.Client{
Timeout: 10 * time.Millisecond,
}
resp, err := client.Get("http://google.com/")
if err != nil {
if os.IsTimeout(err) {
fmt.Println("There was a timeout")
}
panic(err)
}
This works, but os.IsTimeout writes:
// This function predates errors.Is, and the notion of whether an
// error indicates a timeout can be ambiguous. For example, the Unix
// error EWOULDBLOCK sometimes indicates a timeout and sometimes does not.
// New code should use errors.Is with a value appropriate to the call
// returning the error, such as os.ErrDeadlineExceeded.
if errors.Is(err, os.ErrDeadlineExceeded) {
fmt.Println("There was a timeout 2")
}
Which doesn't work. I tried debugging, but there wasn't an easy way for me to see how to check for the specific error type. Coming from.NET I could directly see the exception type, and check for that, how do I go about this in the future?
You would not do it this way nowadays and setting a timeout on the client is left as per the api stability guarantee.
Today, you'd set up a client and do a request with a context.WithTimeout
.
TL;DR: The context will be "Done" if the request times out before a response is received and not be "Done" if the response is returned before the timeout is reached.
package main
import (
"context"
"io"
"log"
"net/http"
"net/url"
"time"
)
func main() {
// Start a http server to test the timeout
srv := setupServer()
defer srv.Shutdown(context.Background())
// Examle 1: Using a context with a timeout for a request
// =======================================================
// create a default http client
client := &http.Client{}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
// Release resources when done
defer cancel()
// Create a new request with our context
req, _ := http.NewRequestWithContext(ctx, "GET", "http://localhost:8080/hellotimeout", nil)
// Start a timer
start := time.Now()
// Make the request
_, err := client.Do(req)
// Check if the request was anything other than a timeout
// Note that we could check for the timeout error here.
// However, we will use a select statement to demonstrate how to handle the timeout.
if urlErr, isURLErr := err.(*url.Error); isURLErr && !urlErr.Timeout() {
log.Printf("Something went wrong: %s", urlErr)
return
}
select {
// If the request times out, the context will be done.
// If the request is completed here, the context will not be done and the default case will be executed.
case <-ctx.Done():
log.Printf("Request timed out after %s", time.Since(start))
default:
log.Printf("Request completed after %s", time.Since(start))
log.Println("Processing response...")
}
// Examle 2: The same, but this time the request does not timeout
// ==============================================================
// Create a new request with our context
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel2()
req, _ = http.NewRequestWithContext(ctx2, "GET", "http://localhost:8080/hello", nil)
// Start a timer
start = time.Now()
// Make the request
resp, err := client.Do(req)
// Check if the request was anything other than a timeout
if urlErr, isURLErr := err.(*url.Error); isURLErr && !urlErr.Timeout() {
log.Printf("Something went wrong: %s", urlErr)
return
}
select {
case <-ctx2.Done():
log.Printf("Request timed out after %s", time.Since(start))
return
default:
log.Printf("Request completed after %s", time.Since(start))
}
log.Println("Processing response...")
io.Copy(log.Writer(), resp.Body)
}
func setupServer() *http.Server {
// Setup a server so we can test the timeout
srv := &http.Server{Addr: ":8080"}
http.Handle("/hellotimeout", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simulate a long running request
time.Sleep(5 * time.Second)
w.Write([]byte("Hello, World!"))
}))
http.Handle("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Respond immediately
w.Write([]byte("Hello, World!"))
}))
// Start the server in "background"
go srv.ListenAndServe()
return srv
}