With the feature set for Go 1.8 now frozen, I thought it would be fun to highlight a few of the more interesting API changes that we can expect to see in Go 1.8 when it’s released around February 1, 2017.
HTTP server connection draining
At the eleventh hour Brad Fitzpatrick closed a nearly four-year-old issue by implementing connection draining on http.Server
. It is now possible to call srv.Close()
to halt an http.Server
immediately, or srv.Shutdown(ctx)
to stop and gracefully drain the server of connections (users of the github.com/tylerb/graceful package will be familiar with this behavior).
As an example, this server shuts down gracefully upon receiving the SIGINT command (hit ^C
).
package main
import (
"context"
"io"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
// subscribe to SIGINT signals
stopChan := make(chan os.Signal)
signal.Notify(stopChan, os.Interrupt)
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
io.WriteString(w, "Finished!")
}))
srv := &http.Server{Addr: ":8081", Handler: mux}
go func() {
// service connections
if err := srv.ListenAndServe(); err != nil {
log.Printf("listen: %s\n", err)
}
}()
<-stopChan // wait for SIGINT
log.Println("Shutting down server...")
// shut down gracefully, but wait no longer than 5 seconds before halting
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
srv.Shutdown(ctx)
log.Println("Server gracefully stopped")
}
Immediately after receiving SIGINT, this server stops accepting new connections and the srv.ListenAndServe()
call returns with http.ErrServerClosed
. The srv.Shutdown(...)
call remains blocked until all outstanding requests have been handled and their underlying connections closed.
More complex behavior can be created using a context
with additional behavior. For example, one may easily enforce a maximum grace period using context.Timeout
. Try cloning this example from github.com/tylerchr/examples/draining and implementing it.
Someone asked how one might write a test to verify that a given handler initiates a Server Push, as http.NewTLSServer
doesn’t start an HTTP/2 server, and httptest.ResponseRecorder
doesn’t implement http.Pusher
. My solution is to wrap httptest.ResponseRecorder
and implement the Pusher
interface manually; there’s an example of this in the repo.
HTTP/2.0 server push via http.Pusher
HTTP/2.0 includes the Server Push feature that enables HTTP/2 servers to proactively send additional HTTP responses to a client for requests that client has not yet made. This aims to reduce the client’s latency by initiating the transfer of resources the server believes a client is likely to need, but for which the client has not yet asked. See the HTTP/2 Server Push Wikipedia page for a concrete example.
If a web server supports HTTP/2, the http.ResponseWriter
provided to handlers will also implement the new http.Pusher
interface. Handlers can use its functionality to trigger a Server Push for a resource by providing an HTTP method, path, and request headers. This “synthetic request” is serviced by the registered handler of the http.Server
that served the original request.
The following Go program handles /index.html
by initiating a push for /static/gopher.png
:
package main
import "net/http"
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
http.Handle("/index.html", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// server push is available if w implements http.Pusher
if p, ok := w.(http.Pusher); ok {
p.Push("/static/gopher.png", nil}
}
// load the main page
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<img src="/static/gopher.png" />`))
}))
http.ListenAndServeTLS(":4430", "cert.pem", "key.pem", nil)
}
If you have a tip build of Go, you can clone this example from github.com/tylerchr/examples/serverpush and observe the push occuring. Here’s what it looks like in Chrome 54:
Obviously, the “Push” prefix in the Initiator column gives away that gopher.png
was received via Server Push. You can also see gopher.png
’s light-blue “Receiving Push” bar appear in the timeline before /index.html
finishes downloading, showing that the image is transferred before the page knows to ask for it. The “Reading Push” phase is triggered later, when the HTML finishes downloading and the <img>
tag is discovered.
database/sql
The database/sql
package has several major additions that give more control over database queries and allow users to take advantage of additional database features.
- Queries take a
context.Context
that can be used to cancel queries.
- Native database column types are exposed via sql.ColumnType.
- Queries can be executed with named parameters if the underlying driver implements support for them.
For more details, see What is new in database/sql? by Daniel Theophanes, who implemented most of the changes.
Dynamic plugins via new package plugin
Preliminary plugin support is implemented via the new plugin
stdlib package. This will allow Go programs to load additional code—like plugins—while the program is running.
This feature seems buggy still, as I can’t manage to write a program that can use it without segfaulting, but it’s supposed to look something like this:
// hexify.go
package main
import "encoding/hex"
func Hexify(in string) string {
return hex.EncodeToString([]byte(in))
}
$ go build -buildmode=shared hexify.go
// produces hexify.so
// main.go
package main
import "plugin"
func main() {
p, _ = plugin.Open("hexify.so")
f := p.Lookup("Hexify")
fmt.Println(f.(func(string) string)("gopher"))
// 676f70686572
}
In this example, hexify.go
implements a function, Hexify
, which is dynamically loaded by the second program. This allows code to be used that isn’t part of the program when it was compiled.
You can imagine a database loading user-defined functions this way, or perhaps extending Caddy with plugins withut having to create a custom build.
Identifier aliasing implemented
UPDATE (4 Nov): Aliases have been backed out of Go 1.8 due to newly-discovered technical implications; see this post from Russ Cox for details. They’ll likely appear in Go 1.9.
Identifier aliasing is a syntax addition for defining multiple types as identical. One use case is to ease refactoring in complex codebases by allowing code to be rearranged without introducing subtle type inequalities, as in this good example from Ian Lance Taylor:
For a concrete example, see the move of golang.org/x/net/context
into the standard library as context
. Because the context package is widely used, and it is difficult to convert all users of the package at once, it is desirable to permit packages using golang.org/x/net/context
to interoperate with packages that have converted to context
.
This interoperability is permitted by code similar to this:
type Foo => pkg.Bar
This syntax defines that, in all practical regards, the type Foo
is identical to the type pkg.Bar
. In other words, Foo
may be used in place of pkg.Bar
anywhere where pkg.Bar
is expected. Using Ian’s context
example, aliasing allows the external golang.net/x/net/context.Context
type and the standard library’s context.Context
to work interchangeably rather than being syntactically-identical but semantically-separate types.
Aliases are available for constants, variables, and functions in addition to types, and follow the conventional export rules.
This is a controversial feature; see issue 16339 and this golang-dev post for details. It’s also too early to tell what idiomatic usage of aliasing might look like, so it is recommended that Go developers use this feature conservatively for now.
New slice sorting API
Universal slice sorting is implemented via new sort.Slice
function. This allows any slice to be sorted by providing a comparator callback rather than creating a specialized sort.Interface
implementation. This function has no return value; like the existing sort functions, it sorts the provided slice in place.
This example sorts a list of worldwide mountains by the elevation of their summits:
type Peak struct {
Name string
Elevation int // in feet
}
peaks := []Peak{
{"Aconcagua", 22838},
{"Denali", 20322},
{"Kilimanjaro", 19341},
{"Mount Elbrus", 18510},
{"Mount Everest", 29029},
{"Mount Kosciuszko", 7310},
{"Mount Vinson", 16050},
{"Puncak Jaya", 16024},
}
// does an in-place sort on the peaks slice, with tallest peak first
sort.Slice(peaks, func(i, j int) bool {
return peaks[i].Elevation >= peaks[j].Elevation
})
// peaks is now sorted
This is possible because the Len()
and Swap(i, j int)
methods of the existing sort.Interface
interface are identical for all slice types, and can be abstracted away. The Less(i, j int)
method can’t be automatically inferred, and is the comparator function passed into sort.Slice
.
Grab Bag
- 87b1aaa The
encoding/base64
encoder now has a strict mode.
- 6ba5b32 The
expvar
route can now be obtained and mounted outside the default mux.
- 003a598 Pseudorandom uint64 values may be generated via
rand.Uint64()
(previously, only uint32 support existed).
- 67ea710 A new
time.Until
function is added to complement the existing time.Since
.