Это руководство для всех, кто хочет раскрыть всю мощь Go не только для серверной части, но и для логики внешнего интерфейса. Начиная с Go 1.11, мы можем скомпилировать наш код Go в файл WebAssembly, который будет выполняться браузером. Код Go сможет взаимодействовать с элементами DOM, и, поскольку этот код скомпилирован, он делает вид, что работает намного быстрее, чем чистый JavaScript.
В нашем примере мы создадим сервис для генерации QR-кодов для наших клиентов.
Шаг 1. Установите Go 1.11
Перейдите на https://golang.org/dl/, загрузите соответствующий двоичный файл и установите его. Этот шаг очень простой, я использую компьютер с Windows, и он отлично работает даже на нем. Чтобы проверить версию Go, используйте эту команду:
PS C:\barcode> go version go version go1.11 windows/amd64
Шаг 2. Подготовьте необходимые файлы
Для быстрого старта мы будем использовать своего рода шаблон HTML для нашего проекта. Откройте корневую папку Go (в моем случае C: \ Go) и перейдите в папку ./misc/wasm:
PS C:\barcode> ls C:\Go\misc\wasm\* . Directory: C:\Go\misc\wasm Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 8/24/2018 8:38 PM 441 go_js_wasm_exec -a---- 8/24/2018 8:38 PM 1046 wasm_exec.html -a---- 8/24/2018 8:38 PM 11905 wasm_exec.js
Скопируйте wasm_exec.html и wasm_exec.js в папку своего проекта:
PS C:\medium\verfio\webassembly> cp C:\Go\misc\wasm\wasm* . PS C:\medium\verfio\webassembly> ls Directory: C:\medium\verfio\webassembly Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 8/24/2018 8:38 PM 1046 wasm_exec.html -a---- 8/24/2018 8:38 PM 11905 wasm_exec.js
Шаг 3. Отредактируйте html-страницу по умолчанию.
Откройте файл wasm_exec.html и добавьте четыре текстовых поля для идентификации наших пользователей. Давайте добавим эти строки перед выделенной ‹button›:
... First Name: <input type="text" id="first" name="first"> Last Name: <input type="text" id="last" name="last"> E-mail: <input type="text" id="mail" name="mail"> Phone: <input type="text" id="phone" name="phone"> <button onClick="run();" id="runButton" >Run</button> ...
Нам также понадобится элемент, в котором будет размещен наш журнал операций, и еще один элемент div для размещения изображения QR-кода:
... <button onClick="run();" id="runButton" disabled>Run</button> <button onClick="clean();" id="clearButton">Clean</button> <div id="target"> </div> <div id="code"> <img id="qrcode" src="" /> </div>
Первый div предназначен для отображения текстовых сообщений, второй будет использоваться для отображения QR-кода в виде изображения png. Кнопка Clean сделает оба div пустыми, и для этого нам нужно создать специальную функцию Clean (). Давайте поместим его после уже существующей функции run () и закомментируем команду clear внутри run ():
async function run() { //console.clear(); await go.run(inst); inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance } async function clean() { document.getElementById("target").innerHTML = ""; document.getElementById("code").innerHTML = ""; }
Шаг 4. Создайте main.go для реализации логики
Внутри этой части мы хотим генерировать запросы и отправлять их на сервер, а также взаимодействовать с элементами DOM для обновления их значений.
package main import ( "log" "net/http" "net/url" "syscall/js" "github.com/dennwc/dom" ) type writer dom.Element // Write implements io.Writer. func (d writer) Write(p []byte) (n int, err error) { node := dom.GetDocument().CreateElement("div") node.SetTextContent(string(p)) (*dom.Element)(&d).AppendChild(node) return len(p), nil } func main() { //get elements to interact with t := dom.GetDocument().GetElementById("target") i := dom.GetDocument().GetElementById("qrcode") //read the First Name value f := js.Global().Get("document").Call("getElementById", "first").Get("value").String() //read the Last Name value l := js.Global().Get("document").Call("getElementById", "last").Get("value").String() //read the Mail value m := js.Global().Get("document").Call("getElementById", "mail").Get("value").String() //read the Phone value p := js.Global().Get("document").Call("getElementById", "phone").Get("value").String() //send the POST request to server and pass the variables _, err := http.PostForm("./wasm_exec.html", url.Values{"first": {f}, "last": {l}, "mail": {m}, "phone": {p}}) if err != nil { log.Fatal(err) } // Generate the name of the QR code file filename := f + l + ".png" // Update the link to the QR code with a new filename i.SetAttribute("src", filename) // Log messages to the specific div logger := log.New((*writer)(t), "", log.LstdFlags) logger.Print("QR code is ready" + "./" + filename) }
Шаг 5. Скомпилируйте main.go в файл WebAssembly.
Нам нужно изменить переменные Go, чтобы компилятор сделал это:
PS C:\medium\verfio\webassembly> $env:GOARCH="wasm"; $env:GOOS="js" PS C:\medium\verfio\webassembly> go build -o test.wasm main.go PS C:\medium\verfio\webassembly> ls Directory: C:\medium\verfio\webassembly Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 9/4/2018 11:29 AM 1501 main.go -a---- 9/4/2018 11:30 AM 7513300 test.wasm -a---- 9/4/2018 11:13 AM 1471 wasm_exec.html -a---- 8/24/2018 8:38 PM 11905 wasm_exec.js PS C:\medium\verfio\webassembly>
Как видите, файл test.wasm - это наш скомпилированный код Go, который мы хотим запустить в браузере пользователя. Ссылка на файл, расположенный внутри wasm_exec.html:
WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then(async (result) => { mod = result.module; inst = result.instance; document.getElementById("runButton").disabled = false; });
Больше ничего менять в этом файле не нужно.
Шаг 6. Создайте файл server.go
В server.go мы хотим обслуживать наши файлы, а также реализовать логику для генерации QR-кода при получении метода POST.
package main import ( "fmt" "image/png" "log" "net/http" "os" "github.com/boombuler/barcode" "github.com/boombuler/barcode/qr" ) func wasmHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { // serve the file during the GET case "GET": http.ServeFile(w, r, "wasm_exec.html") //handle the POST to generate the QR code case "POST": // Call ParseForm() to parse the raw query if err := r.ParseForm(); err != nil { fmt.Fprintf(w, "ParseForm() err: %v", err) return } //initialize variables to produce the QR code f := r.FormValue("first") l := r.FormValue("last") m := r.FormValue("mail") p := r.FormValue("phone") content := f + l + m + p filename := f + l + ".png" // barcod lib creates the QR code qrCode, _ := qr.Encode(content, qr.M, qr.Auto) // Scale the barcode to 200x200 pixels qrCode, _ = barcode.Scale(qrCode, 200, 200) // Create the file in root folder file, _ := os.Create(filename) defer file.Close() png.Encode(file, qrCode) // Default case default: fmt.Fprintf(w, "only GET and POST methods are supported.") } } func main() { // Serve the entire directory mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.Dir("."))) // Specific handler to process the POST request mux.HandleFunc("/wasm_exec.html", wasmHandler) log.Fatal(http.ListenAndServe(":3000", mux)) }
Шаг 7. Протестируйте
Для запуска сервера используйте другое окно терминала (не то же самое, где были изменены переменные GOOS и GOARCH)
PS C:\medium\verfio\webassembly> go run .\server.go
Откройте в браузере localhost: 3000 и перейдите на страницу wasm_exec.html, введите необходимые значения в форму, нажмите кнопку «Выполнить»:
Попробуйте сгенерировать еще пару кодов:
Все изображения находятся в корневой папке вашего сервера:
PS C:\medium\verfio\webassembly> ls Directory: C:\medium\verfio\webassembly Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 9/4/2018 11:54 AM 1049 AliceCooper.png -a---- 9/4/2018 11:52 AM 1045 JohnDow.png -a---- 9/4/2018 11:29 AM 1501 main.go -a---- 9/4/2018 11:54 AM 1027 MattDamon.png -a---- 9/4/2018 11:50 AM 1282 server.go -a---- 9/4/2018 11:30 AM 7513300 test.wasm -a---- 9/4/2018 11:52 AM 1417 wasm_exec.html -a---- 8/24/2018 8:38 PM 11905 wasm_exec.js PS C:\medium\verfio\webassembly>
Вот и все. Быть в курсе!