Go Web Services

Client Requests

Five steps involved in an HTTP request:

  • Dial to establish a TCP connection.

  • TLS handshake (if enabled).

  • Send the request.

  • Read the response headers.

  • Read the response body.

The four main timeouts are the following:

  • net.Dialer.Timeout — Specifies the maximum amount of time a dial will wait for a connection to complete.

  • http.Transport.TLSHandshakeTimeout — Specifies the maximum amount of time to wait for the TLS handshake.

  • http.Transport.ResponseHeaderTimeout — Specifies the amount of time to wait for a server’s response headers.

  • http.Client.Timeout —Specifies the time limit for a request. It includes all the steps, from step 1 (dial) to step 5 (read the response body).

Server Response

Once a connection is accepted, an HTTP response is divided into five steps:

  • Wait for the client to send the request.

  • TLS handshake (if enabled).

  • Read the request headers.

  • Read the request body.

  • Write the response.

The three main timeouts are the following:

  • http.Server.ReadHeaderTimeout — A field that specifies the maximum amount of time to read the request headers

  • http.Server.ReadTimeout—A field that specifies the maximum amount of time to read the entire request

  • http.TimeoutHandler — A wrapper function that specifies the maximum amount of time for a handler to complete

For production-grade applications, we need to make sure not to use default HTTP clients and servers. Otherwise, requests may be stuck forever due to an absence of time- outs or even malicious clients that exploit the fact that our server doesn’t have any timeouts.

All incoming HTTP requests are served in their own goroutine. For busy servers, this means it’s very likely that the code in or called by your handlers will be running concurrently. While this helps make Go blazingly fast, the downside is that you need to be aware of (and protect against) race conditions when accessing shared resources from your handlers.

Response Headers

When sending a response Go will automatically set three system-generated headers for you: Date and Content-Length and Content-Type.

The Content-Type header is particularly interesting. Go will attempt to set the correct one for you by content sniffing the response body with the http.DetectContentType() function. If this function can’t guess the content type, Go will fall back to setting the header Content-Type: application/octet-stream instead.

The http.DetectContentType() function generally works quite well, but a common gotcha for web developers new to Go is that it can’t distinguish JSON from plain text. So, by default, JSON responses will be sent with a Content-Type: text/plain; charset=utf-8 header. You can prevent this from happening by setting the correct header manually like so:

 w.Header().Set("Content-Type", "application/json") 

The Del() method doesn’t remove system-generated headers. To suppress these, you need to access the underlying header map directly and set the value to nil.

When you’re using the Add(), Get(), Set() and Del() methods on the header map, the header name will always be canonicalised using the textproto.CanonicalMIMEHeaderKey() function.

Connection Timeouts

srv := &http.Server{
  ...
  // Add Idle, Read and Write timeouts to the server. 
  IdleTimeout: time.Minute,
  ReadTimeout: 5 * time.Second,
  WriteTimeout: 10 * time.Second, 
}
  • IdleTimeout - By default, Go enables keep-alives on all accepted connections. This helps reduce latency (especially for HTTPS connections) because a client can reuse the same connection for multiple requests without having to repeat the handshake. By default Go keeps connection open for 3 mins. There is no way to increase this cut-off above 3 minutes (unless you roll your own net.Listener), but you can reduce it via the IdleTimeout setting.

  • ReadTimeout - specify time to read headers and body. Setting a short ReadTimeout period helps to mitigate the risk from slow-client attacks which could otherwise keep a connection open indefinitely by sending partial, incomplete, HTTP(S) requests.

  • Write Timeout - will close the underlying connection if our server attempts to write to the connection after a given period. But this behaves slightly differently depending on the protocol being used.

    • For HTTP connections, if some data is written to the connection more than 10 seconds after the read of the request header finished, Go will close the underlying connection instead of writing the data.

    • For HTTPS connections, if some data is written to the connection more than 10 seconds after the request is first accepted, Go will close the underlying connection instead of writing the data. Therefore, the idea of WriteTimeout is generally not to prevent long-running handlers, but to prevent the data that the handler returns from taking too long to write.

The http.Server object also provides a ReadHeaderTimeout setting. This works in a similar way to ReadTimeout, except that it applies to the read of the HTTP(S) headers only.

MaxHeaderBytes field, which you can use to control the maximum number of bytes the server will read when parsing request headers. By default, Go allows a maximum header length of 1MB.

Graceful Shutdown

Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down.

It’s important to explain upfront that some signals are catchable and others are not. Catachable signals can be intercepted by our application and either ignored, or used to trigger a certain action (such as a graceful shutdown). Other signals, like SIGKILL, are not catchable and cannot be intercepted.

When we receive a SIGINT or SIGTERM signal, we instruct our server to stop accepting any new HTTP requests, and give any in-flight requests a ‘grace period’ of 5 seconds to complete before the application is terminated.

  • The server.Shutdown(ctx) method does not wait for any background tasks to complete, nor does it close hijacked long-lived connections like WebSockets. Instead, you will need to implement your own logic to coordinate a graceful shutdown of these things. We’ll look at some techniques for doing this later in the book.

Base64

Base64-encoding data ensures it won't contain any special or unpredictable characters, which is useful for situations such as passing data to a URL or storing it in a cookie. Remember that although Base64- encoded data looks encrypted, it is not you can easily decode Base64- encoded data back to the original text with little effort. There are online tools that do this for you.

Last updated