- Введение
- Среды
- Использование WebView2
- main.go
- Запуск локального сервера
- main.go
- Вызов Go из TypeScript
- global.d.ts
- main.page.ts
- main.go
- CORS(Cross-Origin Resource Sharing)
- GET, POST, PUT, DELETE, OPTIONS
- Ошибка
- [Server-Side] trustedCors.go (Failed)
- [Server-Side] webrequestHandler.go
- [Client-Side] main.page.ts
- trustedCors.go (OK)
- Preflight
- CORS-безопасный метод
- WebSocket
- trustedCors.go
- [Server-Side] websocketHandler.go
- [Client-Side] main.page.ts
Введение
Иногда я хочу автоматически сделать что-то на HTML и TypeScript.
Для этого я использую WebView2, как показано ниже.
- [WPF][WebView2] Сохранить линейные графики как изображения
Можно ли сделать что-то подобное с помощью Go?
Среды
- Go ver.go1.18.2 windows/amd64
- WebView2 Runtime ver.101.0.1210.53
Использование WebView2
Я могу создавать GUI-приложения, используя технологии HTML.
Они условно делятся на два типа: один — для отображения HTML-файлов с помощью веб-браузера, а другой — для создания специализированного пользовательского интерфейса.
В этот раз я выбираю первый.
Для этого я могу использовать «jchv/go-webview2».
- jchv/go-webview2 — GitHub
После установки WebView2 Runtime и выполнения «github.com/jchv/go-webview2», я просто пишу вот так.
main.go
package main
import (
"log"
"os"
"path/filepath"
"github.com/jchv/go-webview2"
)
func main() {
w := webview2.NewWithOptions(webview2.WebViewOptions{
Debug: true, // To display the development tools
AutoFocus: true,
WindowOptions: webview2.WindowOptions{
Title: "webview example",
Width: 200,
Height: 200,
IconId: 2, // icon resource id
Center: true,
},
})
if w == nil {
log.Fatalln("Failed to load webview.")
}
defer w.Destroy()
// The window will be displayed once and then resized to this size.
w.SetSize(600, 600, webview2.HintNone)
// load a local HTML file.
c, err := os.Getwd()
if err != nil {
log.Fatalln(err.Error())
}
w.Navigate(filepath.Join(c, "templates/index.html"))
w.Run()
}
Запуск локального сервера
Я мог показывать локальные HTML-файлы.
Но я не мог загружать файлы JavaScript или CSS.
Кроме того, некоторые API можно было использовать только на «http://localhost:XXXX» или «https://XXX», например MediaStream.
Поэтому я запускаю локальный сервер и WebView2 в главной функции.
main.go
package main
import (
"html/template"
"log"
"net/http"
"sync"
"github.com/jchv/go-webview2"
)
type templateHandler struct {
once sync.Once
filename string
templ *template.Template
}
func main() {
w := webview2.NewWithOptions(webview2.WebViewOptions{
Debug: true, // To display the development tools
AutoFocus: true,
WindowOptions: webview2.WindowOptions{
Title: "webview example",
Width: 200,
Height: 200,
IconId: 2, // icon resource id
Center: true,
},
})
if w == nil {
log.Fatalln("Failed to load webview.")
}
defer w.Destroy()
// Launch a local web server on another goroutine
go handleHTTPRequest()
w.SetSize(600, 600, webview2.HintNone)
w.Navigate("http://localhost:8082/")
w.Run()
}
func handleHTTPRequest() {
http.Handle("/css/", http.FileServer(http.Dir("templates")))
http.Handle("/js/", http.FileServer(http.Dir("templates")))
http.Handle("/", &templateHandler{filename: "index.html"})
log.Fatal(http.ListenAndServe("localhost:8082", nil))
}
func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t.once.Do(func() {
t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
})
t.templ.Execute(w, nil)
}
Вызов Go из TypeScript
Возможно, потому что это очень простая библиотека, поэтому ее функций не слишком много.
Я могу вызывать функции Go из TypeScript следующим образом.
global.d.ts
declare function callGo();
main.page.ts
export function callGo() {
window.callGo();
}
main.go
...
w.Bind("callGo", func() {
log.Println("called")
})
w.Navigate("http://localhost:8082/")
w.Run()
}
...
Но я не смог найти способ добавить некоторые аргументы.
И я также не смог найти способ вызова функций TypeScript(JavaScript).
Но поскольку они работают на локальном сервере, я могу отправлять веб-запросы и пакеты html/шаблонов 😛
- go-webview2/main.go — jchv/go-webview2 — GitHub
- webview/main.go — webview/webview — GitHub
CORS(Cross-Origin Resource Sharing)
Сначала я думал использовать WebView2 для отображения страницы WebRTC, которую я создал в прошлый раз.
Однако из-за того, что я получил ошибку CORS, я решил исправить ее.
По соображениям безопасности, если я отправляю запросы на кросс-оригинальный сайт со скриптами, который работает в моем веб-браузере, они должны иметь правильный заголовок CORS.
- Кросс-оригинальный обмен ресурсами (CORS) — HTTP|MDN
- 3.2. Протокол CORS — Fetch Standard
ASP.NET Core имеет встроенную функцию для стандартной обработки CORS.
- Включение кросс-оригинальных запросов (CORS) в ASP.NET Core — Microsoft Docs
В Go также есть несколько библиотек для этого.
- rs/cors — GitHub
Но я пытаюсь добавить CORS-заголовки самостоятельно.
GET, POST, PUT, DELETE, OPTIONS
Чтобы избежать ошибок CORS, я должен добавить некоторые заголовки в заголовок ответа.
Ошибка
Access to fetch at 'http://localhost:8083/req' from origin 'http://localhost:8082' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Сначала я попробовал установить все целевые источники в заголовке «Access-Control-Allow-Origin», как показано ниже.
[Server-Side] trustedCors.go (Failed)
package main
import (
"log"
"net/http"
)
func SetCORS(w *http.ResponseWriter) {
(*w).Header().Set("Access-Control-Allow-Origin", "http://localhost:8082, http://localhost:8083")
(*w).Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
(*w).Header().Set("Access-Control-Allow-Headers", "*")
}
[Server-Side] webrequestHandler.go
package main
import (
"fmt"
"net/http"
)
func HandleWebrequest(w http.ResponseWriter, r *http.Request) {
SetCORS(&w)
switch r.Method {
case http.MethodGet:
fmt.Fprintln(w, "GET REQUEST")
case http.MethodPost:
fmt.Fprintln(w, "POST REQUEST")
case http.MethodPut:
fmt.Fprintln(w, "PUT REQUEST")
case http.MethodDelete:
fmt.Fprintln(w, "DELETE REQUEST")
default:
fmt.Fprintf(w, "%s REQUEST", r.Method)
}
}
[Client-Side] main.page.ts
export function sendGetRequest() {
fetch("http://localhost:8083/req",
{
method: "GET",
})
.then(res => res.text())
.then(txt => console.log(txt))
.catch(err => console.error(err));
}
export function sendPostRequest() {
fetch("http://localhost:8083/req",
{
method: "POST",
body: JSON.stringify({ messageType: "text", data: "Hello" }),
})
.then(res => res.text())
.then(txt => console.log(txt))
.catch(err => console.error(err));
}
export function sendPutRequest() {
fetch("http://localhost:8083/req",
{
method: "PUT",
})
.then(res => res.text())
.then(txt => console.log(txt))
.catch(err => console.error(err));
}
export function sendDeleteRequest() {
fetch("http://localhost:8083/req",
{
method: "DELETE",
})
.then(res => res.text())
.then(txt => console.log(txt))
.catch(err => console.error(err));
}
export function sendOptionsRequest() {
fetch("http://localhost:8083/req",
{
method: "OPTIONS",
})
.then(res => res.text())
.then(txt => console.log(txt))
.catch(err => console.error(err));
}
Но при отправке запросов я получаю ошибку.
Access to fetch at 'http://localhost:8083/req' from origin 'http://localhost:8082' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8082, http://localhost:8083', but only one is allowed. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Потому что заголовок «Access-Control-Allow-Origin» должен содержать одно значение.
Поэтому я изменил код.
trustedCors.go (OK)
var (
trustedOrigins = []string{"http://localhost:8082", "http://localhost:8083"}
)
func SetCORS(w *http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
targetOrigin := ""
for _, o := range trustedOrigins {
if origin == o {
targetOrigin = o
break
}
}
(*w).Header().Set("Access-Control-Allow-Origin", targetOrigin)
(*w).Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
(*w).Header().Set("Access-Control-Allow-Headers", "*")
}
- 3. Расширения HTTP — Fetch Standard
- Кросс-оригинальный обмен ресурсами (CORS) — HTTP|MDN
Preflight
Если веб-запрос не является так называемым «простым запросом», веб-браузер посылает «preflight request», чтобы проверить, можно ли его отправить до отправки запроса.
Чтобы управлять запросом preflight, не нужно ничего делать на стороне клиента и на стороне сервера.
- Preflight request — MDN Web Docs Glossary: Определения терминов, связанных с веб|MDN
CORS-безопасный метод
Если я не добавляю «PUT», «DELETE», «OPTIONS» в заголовок «Access-Control-Allow-Methods», я могу блокировать их запросы.
Но я не могу блокировать запросы «GET», «POST» и «HEAD».
Потому что они являются так называемыми «CORS-запрещенными методами».
Поэтому даже если я установлю заголовок «Access-Control-Allow-Methods» как «PUT, DELETE, OPTIONS», я все равно могу отправлять запросы «GET» или «POST».
Запросы «preflight» тоже не блокируются.
- Блокируют ли браузеры запросы POST, если POST не указан в значении Access-Control-Allow-Methods ответа preflight OPTIONS? — StackOverflow
- 4.8. CORS-предупредительная выборка — Стандарт выборки
WebSocket
Как насчет WebSocket?
- [Golang] Попробуйте WebSocket
Если запрос на подключение WebSocket нарушает CORS, в «upgrader.Upgrade» из «room.go» возникнет ошибка.
websocket: request origin not allowed by Upgrader.CheckOrigin
Чтобы избежать этого, я должен реализовать функцию «CheckOrigin».
trustedCors.go
...
func ValidateCORS(r *http.Request) bool {
origin := r.Header.Get("Origin")
for _, o := range trustedOrigins {
if o == origin {
return true
}
}
return false
}
[Server-Side] websocketHandler.go
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return ValidateCORS(r)
},
}
type websocketMessage struct {
MessageType string `json:"messageType"`
Data string `json:"data"`
}
func HandleWebsocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
// Close the connection when the for-loop operation is finished.
defer conn.Close()
message := &websocketMessage{}
for {
messageType, raw, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
} else if err := json.Unmarshal(raw, &message); err != nil {
log.Println(err)
return
}
log.Println(messageType)
conn.WriteJSON(message)
}
}
[Client-Side] main.page.ts
...
let ws: WebSocket|null = null;
export function connectWs() {
ws = new WebSocket("ws://localhost:8083/ws");
ws.onmessage = data => {
const message = JSON.parse(data.data);
console.log(message);
};
}
export function closeWs() {
ws?.close();
}
- Как добавить доверенное происхождение в CheckOrigin gorilla websocket? — StackOverflow
- пакет websocket — github.com/gorilla/websocket — pkg.go.dev