In this part I will try to briefly go through the missing places of our very simplified web application on Go.
A little twist: I do not like the word “middleware”. The concept of a wrapper exists from the beginning of the calculations, so I see no need to invent new words for it.
But to put it aside, let's say we needed authentication for a specific URL. Now our main page handler looks like this:
func indexHandler(m *model.Model) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, indexHTML) }) }
We can write a function that takes http.Handler
as an argument and returns (another) http.Handler
. The returned handler checks whether the user is authenticated, using m.IsAuthenticated()
(no matter what happens there) and redirects the user to the login page or executes the original handler by calling his ServeHTTP()
method.
func requireLogin(h http.Handler, m *model.Model) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !m.IsAuthenticated(r) { http.Redirect(w, r, loginURL, http.StatusFound) return } h.ServeHTTP(w, r) }) }
With this in mind, the handler registration will now look like this:
http.Handle("/", requireLogin(indexHandler(m), m))
In this way, you can wrap the handlers in any number of layers needed and this very flexible approach. Everything from setting headers to compressing the output can be accomplished using a wrapper. Also note that we can pass any arguments we need, for example, our *model.Model
.
At some point, we may need parameters in the URL, for example, /Person/3
, where 3
is the person identifier. The standard Go library does not provide anything for this, leaving this task as an exercise for the developer. The software component responsible for such a thing is called Mux , i.e. "Multiplexer", or "router", and it can be replaced with a non-standard implementation. The router also implements the ServeHTTP()
method, which means that it satisfies the http.Handler interface, that is, it is a handler.
A very popular router option is Gorilla Mux . He can delegate entire paths to places where more flexibility is required. For example, we can decide that everything, from /person
and below, is handled by the Gorilla router, and we want everything else to be authenticated, then it might look like this:
// import "github.com/gorilla/mux" pr := mux.NewRouter().PathPrefix("/person").Subrouter() pr.Handle("/{id}", personGetHandler(m)).Methods("GET") pr.Handle("/", personPostHandler(m)).Methods("POST") pr.Handle("/{id}", personPutHandler(m)).Methods("PUT") http.Handle("/person/", requireLogin(pr))
Note: I found that the slashes at the end are important, and the rules about when they are required are a bit confusing.
There are many other implementations of the router / multiplexer. The beauty is that without being tied to any framework, we can choose the router that best suits us, or write our own (they are not difficult to implement).
One of the most elegant things in Go is that the compiled program is a single binary file, not a large pile of files, as is often the case with most scripting languages ​​and even some compiled ones. But if our program depends on static files (JS, CSS, images and other files), we will need to copy them to the server during deployment.
We can save this characteristic - “one binary” - of our program by including static as part of the binary file itself. To do this, there is the go-bindata project and his nephew go-bindata-assetsfs .
Since packing a static file into a binary file is somewhat beyond what go build
can do, we will need some kind of script that will take care of this. Personally, I prefer to use a proven and "scary" make
, and it is not so rare to see the “Makefile” in the Go project.
Here is an example of a suitable Makefile rule:
ASSETS_DIR = "assets" build: @export GOPATH=$${GOPATH-~/go} && \ go get github.com/jteeuwen/go-bindata/... github.com/elazarl/go-bindata-assetfs/... && \ $$GOPATH/bin/go-bindata -o bindata.go -tags builtinassets ${ASSETS_DIR}/... && \ go build -tags builtinassets -ldflags "-X main.builtinAssets=${ASSETS_DIR}"
This rule creates the bindata.go
file, which is placed in the same directory as main.go
, so it becomes part of the main
package. main.go
somehow finds out that static files are embedded - this is done using the -ldflags "-X main.builtinAssets=${ASSETS_DIR}"
, so we can assign values ​​to variables at the compilation stage. This means that now our code can check the value of builtinAssets
to decide what to do next, for example:
if builtinAssets != "" { log.Printf("Running with builtin assets.") cfg.UI.Assets = &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: builtinAssets} } else { log.Printf("Assets served from %q.", assetsPath) cfg.UI.Assets = http.Dir(assetsPath) }
The second important thing is that we define a build tag called builtinassets
. We also tell go-bindata about it, which means “compile me only when builtinassets are installed”, and this allows you to control the conditions under which bindata.go
needs to be compiled (which contains our static code as Go code).
Finally (in order, and not in importance), I want to briefly mention the packaging of web statics. To describe this properly, there is enough material for a whole new series of articles, and this would have nothing to do with Go. But I can at least list some points.
Basically, you can make concessions - install npm and customize the package.json
file.
Once npm is installed, the Babel command-line compiler, babel-cli
, is babel-cli
, which is one of the ways JavaScript is being babel-cli
.
A more complicated, somewhat disappointing, but ultimately more flexible way is to use a webpack . Webpack will not only pretranslate JS-code, but also combine all JS-files into one, and also minimize it.
import
and export
keywords, but there is no implementation, and even Babel assumes that someone else will implement them for you. In the end, I settled on SystemJS . Some complication with SystemJS is that Babel's Intra Browser Transfiguration should be something that SystemJS understands, so I had to use its Babel plugin. Webpack, in turn (if I understood correctly), provides its own implementation of module support, so when packaging SystemJS is not needed. In any case, it was rather unpleasant.I would say that in the example that I am describing in this four-part series, Go certainly shines, but JavaScript is not really. But as soon as I overcame the initial difficulties in order to make it all work, React / JSX turned out to be simple and perhaps even enjoyable to work with.
On this I, perhaps, finish. I hope the articles were helpful to you.
Source: https://habr.com/ru/post/329622/
All Articles