
If you are developing web applications on go, then this article may be of interest to you. Before switching to go, I mostly programmed in PHP and I always liked the fact that you can save the file, reload the page and see the result, which is already generated by the new code. Large programs on go can be compiled for several tens of seconds, which is very fast, but still noticeable. Is it possible to make an analogue of Java hotswap (replacing the method body at runtime), because Go compiles into native code? The answer is yes, perhaps, but only for development. At the moment I do not know about the ready-made tools that would allow to automate it. In this article, I would like to demonstrate the live-
restoring proof-of-concept using the
plugin package
in go1.8beta2 and the
github.com/bouk/monkey package. An inquisitive reader probably already guesses what we will do.
Training
So, before starting, we need:
- go1.8beta2 or newer
- Linux (in go1.9 should work in macOS)
- Package github.com/bouk/monkey
main idea
For go there is a library for monkey patching'a code, which allows you to replace the body of functions and methods "on the fly" called
monkey . It works by rewriting the body of the function and inserting its code there. Read more in English about the implementation can be read here:
bouk.co/blog/monkey-patching-in-go . In order for this library to work correctly, the project must be built with the inlining code disabled: “go build -gcflags = -l”. This library suits us, but there is a problem: where can I get a pointer to a method with a new code instead of the old one?
Plugins in go1.8
In the version go1.8, the plugin mechanism will appear - the ability to compile the go code as a .so file that can be dynamically loaded into an already running application and start using it. You can read more about it here:
tip.golang.org/pkg/plugin . The plugin is built with dynamic linking and may differ slightly in how it works, from the usual go code. However, as a rule, there are no differences in the work.
')
Putting it all together
Create a web server with two packages: main and handlers (import omitted for brevity).
// main.go package main func main() { http.HandleFunc("/example", handlers.Example) http.HandleFunc("/reload", handlers.Reload) http.ListenAndServe(":9999", nil) } // handlers/example.go package handlers func Example(rw http.ResponseWriter, req *http.Request) { req.ParseForm() fmt.Fprintf(rw, "Hello, %s!", req.Form.Get("name")) } func Reload(rw http.ResponseWriter, req *http.Request) { p, _ := plugin.Open("handlers.so") sym, _ := p.Lookup("Example") monkey.Patch(Example, sym) }
Run this web server and check that it works:
$ curl 'http://localhost:9999/example?name=Yuriy' Hello, Yuriy!
Create another package where we put the plugin (the location of the directory does not matter, in the example I will put this file in handlers / plug / main.go):
package main import ( "fmt" "net/http" ) import "C" func Example(rw http.ResponseWriter, req *http.Request) { req.ParseForm() fmt.Fprintf(rw, "Hello modified, %s!", req.Form.Get("name")) }
Note the
import "C"
, which was not in the original package. This import is needed for the correct operation of the plugin.
Build this plugin with the following command:
$ go build -buildmode plugin -o handlers.so
The file handlers.so needs to be placed in the directory from which you started the web server, since the relative path to handlers.so is specified in the function handlers.Reload.
Now let's try hot-swap the code:
$ curl 'http:
If there were no errors in the web server log, then everything went well and you can check that the code has been updated:
$ curl 'http:
Conclusion
I posted the full example code on github:
github.com/YuriyNasretdinov/hotreload-example . I would like to note that this is only a proof-of-concept and there are a lot of things that would be worth finalizing before you could really use it:
- There is no easy way to patch in this way any function or method — you need to have in advance a list of functions that can be passed to monkey.Patch.
- The collected plugins take as much as the corresponding go program, and in some cases compiling the plug-in can take a comparable amount of time with compiling the entire program. It's hard to do something about it, except by reducing the connectivity in the application.
- monkey.Patch is not thread-safe and in theory can call SEGFAULT in an application.
However, I hope you enjoyed this article and learned something new for yourself. Congratulations to all with the advent of the New Year holidays!