golangでの実際のコードのリロード

外出先でWebアプリケーションを開発している場合は、この記事が参考になるかもしれません。 移行する前に、私は主にPHPでプログラミングしました。ファイルを保存し、ページをリロードして、新しいコードによって既に生成された結果を確認できるという事実が常に好きでした。 外出先での大規模なプログラムは数十秒でコンパイルできます。これは非常に高速ですが、具体的なものです。 Goはネイティブコードにコンパイルされるため、Javaホットスワップの類似物を作成することは可能ですか(実行時にメソッド本体を置き換える)? おそらくその答えはイエスですが、それは開発のためだけです。 現時点では、これを自動化できる既製のツールについては知りません。 この記事では、go1.8beta2プラグインパッケージとgithub.com/bouk/monkeyパッケージを使用して、ライブリブートの概念実証を示したいと思います。 好奇心reader盛な読者は、おそらく私たちが何をするかをすでに推測しているでしょう。



準備する



そのため、開始する前に、次のものが必要です。



  1. go1.8beta2以降
  2. Linux(go1.9ではmacOSでも動作するはずです)
  3. Github.com/bouk/monkeyパッケージ


主なアイデア



goには、モンキーと呼ばれる関数とメソッドの本体をその場で置き換えることができる、モンキーパッチコードのライブラリがあります。 関数の本体を書き換え、そこにコードを挿入することで機能します。 英語での実装の詳細については、 bouk.co / blog / monkey-patching-in-goをご覧ください 。 このライブラリを正しく機能させるには、インラインコードを無効にしてプロジェクトをビルドする必要があります:“ go build -gcflags = -l”。 このライブラリは私たちに適していますが、問題があります。古いコードの代わりに新しいコードでメソッドへのポインタを取得する場所はどこですか?



go1.8のプラグイン



バージョンgo1.8では、プラグインメカニズムが表示されます。これは、goコードを.soファイルとしてコンパイルする機能で、既に実行中のアプリケーションに動的にロードして使用を開始できます。 詳しくはtip.golang.org/pkg/pluginをご覧ください 。 プラグインは動的リンクで構築されており、通常のgoコードとは動作が少し異なる場合があります。 ただし、原則として、作業に違いはありません。



すべてをまとめる



メインとハンドラーの2つのパッケージでWebサーバーを作成しましょう(簡潔にするためにインポートは省略されています)。



//  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) }
      
      





このWebサーバーを実行し、動作することを確認します。



 $ curl 'http://localhost:9999/example?name=Yuriy' Hello, Yuriy!
      
      





プラグインを配置する別のパッケージを作成しましょう(ディレクトリの場所は関係ありません。この例では、このファイルを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")) }
      
      





元のパッケージにはなかったimport "C"



注意してください。 このインポートは、プラグインが正しく機能するために必要です。



次のコマンドでこのプラグインをビルドします。



 $ go build -buildmode plugin -o handlers.so
      
      





handlers.soへの相対パスはhandlers.Reload関数で指定されているため、handlers.soファイルは、Webサーバーを起動したディレクトリに配置する必要があります。



次に、コードをホットスワップしてみます。



 $ curl 'http://localhost:9999/reload'
      
      





Webサーバーのログにエラーがなければ、すべてがうまくいき、コードが更新されたことを確認できます。



 $ curl 'http://localhost:9999/example?name=Yuriy' Hello modified, Yuriy!
      
      





おわりに



完全なサンプルコードをgithubに投稿しました: github.com/YuriyNasretdinov/hotreload-example これは単なる概念実証であり、実際に使用する前に最終決定する価値のあるものがたくさんあることに注意してください。



  1. この方法で関数やメソッドにパッチを当てる簡単な方法はありません。事前にmonkey.Patchに渡すことができる関数のリストが必要です。
  2. アセンブルされたプラグインは、対応するgoプログラムと同じ量を使用し、場合によっては、プラグインのコンパイルにプログラム全体のコンパイルに匹敵する時間がかかることがあります。 アプリケーションの接続を減らすことを除いて、これについて何もするのは難しいです。
  3. monkey.Patchはスレッドセーフではなく、理論的にはアプリケーションでSEGFAULTを呼び出すことができます。


それでも、この記事を楽しんで、自分にとって何か新しいことを学んだことを願っています。 お正月おめでとうございます!



All Articles