Это руководство для всех, кто хочет раскрыть всю мощь 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>

Вот и все. Быть в курсе!