mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-08 12:00:43 +08:00
Move v2 assetserver to v3
This commit is contained in:
parent
13be4a333f
commit
15f602f867
@ -1,6 +1,8 @@
|
||||
module binding
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.0
|
||||
|
||||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
|
||||
|
||||
@ -17,7 +19,6 @@ require (
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.2 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.5.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
@ -25,6 +26,4 @@ require (
|
||||
|
||||
replace github.com/wailsapp/wails/v3 => ../..
|
||||
|
||||
replace github.com/wailsapp/wails/v2 => ../../../v2
|
||||
|
||||
replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01
|
||||
|
@ -1,6 +1,8 @@
|
||||
module frameless
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.0
|
||||
|
||||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
|
||||
|
||||
@ -17,12 +19,9 @@ require (
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.2 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.5.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/wailsapp/wails/v3 => ../..
|
||||
|
||||
replace github.com/wailsapp/wails/v2 => ../../../v2
|
||||
|
@ -1,6 +1,8 @@
|
||||
module plugin_demo
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.0
|
||||
|
||||
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
|
||||
|
||||
@ -23,7 +25,6 @@ require (
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.2 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.5.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
@ -42,5 +43,3 @@ require (
|
||||
)
|
||||
|
||||
replace github.com/wailsapp/wails/v3 => ../..
|
||||
|
||||
replace github.com/wailsapp/wails/v2 => ../../../v2
|
||||
|
@ -28,7 +28,8 @@ require (
|
||||
github.com/samber/lo v1.37.0
|
||||
github.com/tc-hib/winres v0.1.6
|
||||
github.com/wailsapp/go-webview2 v1.0.2
|
||||
github.com/wailsapp/wails/v2 v2.5.1
|
||||
github.com/wailsapp/mimetype v1.4.1
|
||||
golang.org/x/net v0.7.0
|
||||
golang.org/x/sys v0.9.0
|
||||
modernc.org/sqlite v1.21.0
|
||||
)
|
||||
@ -56,7 +57,6 @@ require (
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||
github.com/leaanthony/slicer v1.5.0 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.5 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-zglob v0.0.4 // indirect
|
||||
@ -70,14 +70,12 @@ require (
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/sajari/fuzzy v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/term v0.9.0 // indirect
|
||||
@ -99,6 +97,4 @@ require (
|
||||
mvdan.cc/sh/v3 v3.7.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/wailsapp/wails/v2 => ../v2
|
||||
|
||||
replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01
|
||||
|
@ -232,7 +232,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
|
||||
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
|
||||
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
|
||||
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
||||
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ=
|
||||
github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
|
||||
|
@ -32,8 +32,6 @@ func isLocalBuild() bool {
|
||||
|
||||
// RelativePath returns a qualified path created by joining the
|
||||
// directory of the calling file and the given relative path.
|
||||
//
|
||||
// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal`
|
||||
func RelativePath(relativepath string, optionalpaths ...string) string {
|
||||
_, thisFile, _, _ := runtime.Caller(1)
|
||||
localDir := filepath.Dir(thisFile)
|
||||
|
@ -8,6 +8,5 @@ require (
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/leaanthony/slicer v1.5.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
)
|
||||
|
@ -5,8 +5,6 @@ github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5Az
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 h1:Wn+nhnS+VytzE0PegUzSh4T3hXJCtggKGD/4U5H9+wQ=
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.0 h1:T5gqG98Xr8LBf69oxlPkhpsFD59w2SnqUZk6XHj8Zoc=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.0/go.mod h1:OAfO5bP0TSUvCIHZYc6Dqfow/9RqxzHvYtmhWPpo1c0=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,7 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
build:
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,7 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
build:
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -39,8 +39,9 @@ tasks:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -8,10 +8,8 @@ require (
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/leaanthony/slicer v1.5.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,7 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
build:
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -38,9 +38,10 @@ tasks:
|
||||
platforms:
|
||||
- darwin
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp
|
||||
- task: post-build
|
||||
env:
|
||||
CGO_CFLAGS: "-mmacosx-version-min=10.13"
|
||||
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
|
||||
@ -51,6 +52,7 @@ tasks:
|
||||
- windows
|
||||
cmds:
|
||||
- task: pre-build
|
||||
- task: build-frontend
|
||||
- go build -gcflags=all="-N -l" -o bin/testapp.exe
|
||||
- task: post-build
|
||||
|
||||
|
@ -11,11 +11,9 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
@ -16,9 +16,8 @@ import (
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
|
||||
|
||||
wailsruntime "github.com/wailsapp/wails/v3/internal/runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
@ -72,10 +71,10 @@ func New(appOptions Options) *App {
|
||||
|
||||
result.Events = NewWailsEventProcessor(result.dispatchEventToWindows)
|
||||
|
||||
opts := assetserveroptions.Options{
|
||||
opts := &assetserver.Options{
|
||||
Assets: appOptions.Assets.FS,
|
||||
Handler: appOptions.Assets.Handler,
|
||||
Middleware: assetserveroptions.Middleware(appOptions.Assets.Middleware),
|
||||
Middleware: assetserver.Middleware(appOptions.Assets.Middleware),
|
||||
}
|
||||
|
||||
// TODO ServingFrom disk?
|
||||
|
@ -128,7 +128,7 @@ import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package application
|
||||
|
||||
import "github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
import "github.com/wailsapp/wails/v3/pkg/assetserver"
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
@ -29,7 +28,7 @@ type linuxWebviewWindow struct {
|
||||
parent *WebviewWindow
|
||||
menubar pointer
|
||||
vbox pointer
|
||||
menu *menu.Menu
|
||||
menu *Menu
|
||||
accels pointer
|
||||
lastWidth int
|
||||
lastHeight int
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/bep/debounce"
|
||||
"github.com/wailsapp/go-webview2/webviewloader"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v3/internal/capabilities"
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
202
v3/pkg/assetserver/assethandler.go
Normal file
202
v3/pkg/assetserver/assethandler.go
Normal file
@ -0,0 +1,202 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Debug(message string, args ...interface{})
|
||||
Error(message string, args ...interface{})
|
||||
}
|
||||
|
||||
//go:embed defaultindex.html
|
||||
var defaultHTML []byte
|
||||
|
||||
const (
|
||||
indexHTML = "index.html"
|
||||
)
|
||||
|
||||
type assetHandler struct {
|
||||
fs iofs.FS
|
||||
handler http.Handler
|
||||
|
||||
logger Logger
|
||||
|
||||
retryMissingFiles bool
|
||||
}
|
||||
|
||||
func NewAssetHandler(options *Options, log Logger) (http.Handler, error) {
|
||||
|
||||
vfs := options.Assets
|
||||
if vfs != nil {
|
||||
if _, err := vfs.Open("."); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subDir, err := FindPathToFile(vfs, indexHTML)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
msg := "no `index.html` could be found in your Assets fs.FS"
|
||||
if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs {
|
||||
rootFolder, _ := FindEmbedRootPath(embedFs)
|
||||
msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vfs, err = iofs.Sub(vfs, path.Clean(subDir))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var result http.Handler = &assetHandler{
|
||||
fs: vfs,
|
||||
handler: options.Handler,
|
||||
logger: log,
|
||||
}
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
result = middleware(result)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
url := req.URL.Path
|
||||
handler := d.handler
|
||||
if strings.EqualFold(req.Method, http.MethodGet) {
|
||||
filename := path.Clean(strings.TrimPrefix(url, "/"))
|
||||
|
||||
d.logDebug("Handling request '%s' (file='%s')", url, filename)
|
||||
if err := d.serveFSFile(rw, req, filename); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if handler != nil {
|
||||
d.logDebug("File '%s' not found, serving '%s' by AssetHandler", filename, url)
|
||||
handler.ServeHTTP(rw, req)
|
||||
err = nil
|
||||
} else {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
d.logError("Unable to handle request '%s': %s", url, err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
} else if handler != nil {
|
||||
d.logDebug("No GET request, serving '%s' by AssetHandler", url)
|
||||
handler.ServeHTTP(rw, req)
|
||||
} else {
|
||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// serveFile will try to load the file from the fs.FS and write it to the response
|
||||
func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error {
|
||||
if d.fs == nil {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
file, err := d.fs.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
statInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
url := req.URL.Path
|
||||
isDirectoryPath := url == "" || url[len(url)-1] == '/'
|
||||
if statInfo.IsDir() {
|
||||
if !isDirectoryPath {
|
||||
// If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on
|
||||
// WebKit WebViews (macOS/Linux).
|
||||
// So we handle this as a specific error
|
||||
return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request")
|
||||
}
|
||||
|
||||
filename = path.Join(filename, indexHTML)
|
||||
|
||||
file, err = d.fs.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
statInfo, err = file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if isDirectoryPath {
|
||||
return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request")
|
||||
}
|
||||
|
||||
var buf [512]byte
|
||||
var n int
|
||||
if _, haveType := rw.Header()[HeaderContentType]; !haveType {
|
||||
// Detect MimeType by sniffing the first 512 bytes
|
||||
n, err = file.Read(buf[:])
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do the custom MimeType sniffing even though http.ServeContent would do it in case
|
||||
// of an io.ReadSeeker. We would like to have a consistent behaviour in both cases.
|
||||
if contentType := GetMimetype(filename, buf[:n]); contentType != "" {
|
||||
rw.Header().Set(HeaderContentType, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
if fileSeeker, _ := file.(io.ReadSeeker); fileSeeker != nil {
|
||||
if _, err := fileSeeker.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("seeker can't seek")
|
||||
}
|
||||
|
||||
http.ServeContent(rw, req, statInfo.Name(), statInfo.ModTime(), fileSeeker)
|
||||
return nil
|
||||
}
|
||||
|
||||
rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size()))
|
||||
|
||||
// Write the first 512 bytes used for MimeType sniffing
|
||||
_, err = io.Copy(rw, bytes.NewReader(buf[:n]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the remaining content of the file
|
||||
_, err = io.Copy(rw, file)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *assetHandler) logDebug(message string, args ...interface{}) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("[AssetHandler] "+message, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *assetHandler) logError(message string, args ...interface{}) {
|
||||
if d.logger != nil {
|
||||
d.logger.Error("[AssetHandler] "+message, args...)
|
||||
}
|
||||
}
|
78
v3/pkg/assetserver/assethandler_external.go
Normal file
78
v3/pkg/assetserver/assethandler_external.go
Normal file
@ -0,0 +1,78 @@
|
||||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func NewExternalAssetsHandler(logger Logger, options Options, url *url.URL) http.Handler {
|
||||
baseHandler := options.Handler
|
||||
|
||||
errSkipProxy := fmt.Errorf("skip proxying")
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
baseDirector := proxy.Director
|
||||
proxy.Director = func(r *http.Request) {
|
||||
baseDirector(r)
|
||||
if logger != nil {
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s'", r.URL)
|
||||
}
|
||||
}
|
||||
|
||||
proxy.ModifyResponse = func(res *http.Response) error {
|
||||
if baseHandler == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if res.StatusCode == http.StatusSwitchingProtocols {
|
||||
return nil
|
||||
}
|
||||
|
||||
if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusMethodNotAllowed {
|
||||
return errSkipProxy
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) {
|
||||
if baseHandler != nil && errors.Is(err, errSkipProxy) {
|
||||
if logger != nil {
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL)
|
||||
}
|
||||
baseHandler.ServeHTTP(rw, r)
|
||||
} else {
|
||||
if logger != nil {
|
||||
logger.Error("[ExternalAssetHandler] Proxy error: %v", err)
|
||||
}
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
}
|
||||
|
||||
var result http.Handler = http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == http.MethodGet {
|
||||
proxy.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if baseHandler != nil {
|
||||
baseHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
||||
})
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
result = middleware(result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
246
v3/pkg/assetserver/assetserver.go
Normal file
246
v3/pkg/assetserver/assetserver.go
Normal file
@ -0,0 +1,246 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
const (
|
||||
runtimeJSPath = "/wails/runtime.js"
|
||||
ipcJSPath = "/wails/ipc.js"
|
||||
runtimePath = "/wails/runtime"
|
||||
capabilitiesPath = "/wails/capabilities"
|
||||
flagsPath = "/wails/flags"
|
||||
)
|
||||
|
||||
type RuntimeAssets interface {
|
||||
DesktopIPC() []byte
|
||||
WebsocketIPC() []byte
|
||||
RuntimeDesktopJS() []byte
|
||||
}
|
||||
|
||||
type RuntimeHandler interface {
|
||||
HandleRuntimeCall(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type AssetServer struct {
|
||||
handler http.Handler
|
||||
runtimeJS []byte
|
||||
ipcJS func(*http.Request) []byte
|
||||
|
||||
logger Logger
|
||||
runtime RuntimeAssets
|
||||
|
||||
servingFromDisk bool
|
||||
appendSpinnerToBody bool
|
||||
|
||||
// Use http based runtime
|
||||
runtimeHandler RuntimeHandler
|
||||
|
||||
// plugin scripts
|
||||
pluginScripts map[string]string
|
||||
|
||||
// GetCapabilities returns the capabilities of the runtime
|
||||
GetCapabilities func() []byte
|
||||
|
||||
// GetFlags returns the application flags
|
||||
GetFlags func() []byte
|
||||
|
||||
assetServerWebView
|
||||
}
|
||||
|
||||
func NewAssetServerMainPage(bindingsJSON string, options *Options, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
|
||||
return NewAssetServer(bindingsJSON, options, servingFromDisk, logger, runtime)
|
||||
}
|
||||
|
||||
func NewAssetServer(bindingsJSON string, options *Options, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
|
||||
handler, err := NewAssetHandler(options, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime)
|
||||
}
|
||||
|
||||
func NewAssetServerWithHandler(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
|
||||
var buffer bytes.Buffer
|
||||
if bindingsJSON != "" {
|
||||
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
||||
}
|
||||
buffer.Write(runtime.RuntimeDesktopJS())
|
||||
|
||||
result := &AssetServer{
|
||||
handler: handler,
|
||||
runtimeJS: buffer.Bytes(),
|
||||
|
||||
// Check if we have been given a directory to serve assets from.
|
||||
// If so, this means we are in dev mode and are serving assets off disk.
|
||||
// We indicate this through the `servingFromDisk` flag to ensure requests
|
||||
// aren't cached in dev mode.
|
||||
servingFromDisk: servingFromDisk,
|
||||
logger: logger,
|
||||
runtime: runtime,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *AssetServer) UseRuntimeHandler(handler RuntimeHandler) {
|
||||
d.runtimeHandler = handler
|
||||
}
|
||||
|
||||
func (d *AssetServer) AddPluginScript(pluginName string, script string) {
|
||||
if d.pluginScripts == nil {
|
||||
d.pluginScripts = make(map[string]string)
|
||||
}
|
||||
pluginName = strings.ReplaceAll(pluginName, "/", "_")
|
||||
pluginName = html.EscapeString(pluginName)
|
||||
pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000))
|
||||
d.pluginScripts[pluginScriptName] = script
|
||||
}
|
||||
|
||||
func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if isWebSocket(req) {
|
||||
// WebSockets are not supported by the AssetServer
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
header := rw.Header()
|
||||
if d.servingFromDisk {
|
||||
header.Add(HeaderCacheControl, "no-cache")
|
||||
}
|
||||
|
||||
path := req.URL.Path
|
||||
switch path {
|
||||
case "", "/", "/index.html":
|
||||
recorder := httptest.NewRecorder()
|
||||
d.handler.ServeHTTP(recorder, req)
|
||||
for k, v := range recorder.HeaderMap {
|
||||
header[k] = v
|
||||
}
|
||||
|
||||
switch recorder.Code {
|
||||
case http.StatusOK:
|
||||
content, err := d.processIndexHTML(recorder.Body.Bytes())
|
||||
if err != nil {
|
||||
d.serveError(rw, err, "Unable to processIndexHTML")
|
||||
return
|
||||
}
|
||||
d.writeBlob(rw, indexHTML, content)
|
||||
|
||||
case http.StatusNotFound:
|
||||
d.writeBlob(rw, indexHTML, defaultHTML)
|
||||
|
||||
default:
|
||||
rw.WriteHeader(recorder.Code)
|
||||
|
||||
}
|
||||
|
||||
case runtimeJSPath:
|
||||
d.writeBlob(rw, path, d.runtimeJS)
|
||||
|
||||
case capabilitiesPath:
|
||||
var data = []byte("{}")
|
||||
if d.GetCapabilities != nil {
|
||||
data = d.GetCapabilities()
|
||||
}
|
||||
d.writeBlob(rw, path, data)
|
||||
|
||||
case flagsPath:
|
||||
var data = []byte("{}")
|
||||
if d.GetFlags != nil {
|
||||
data = d.GetFlags()
|
||||
}
|
||||
d.writeBlob(rw, path, data)
|
||||
|
||||
case runtimePath:
|
||||
if d.runtimeHandler != nil {
|
||||
d.runtimeHandler.HandleRuntimeCall(rw, req)
|
||||
} else {
|
||||
d.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
case ipcJSPath:
|
||||
content := d.runtime.DesktopIPC()
|
||||
if d.ipcJS != nil {
|
||||
content = d.ipcJS(req)
|
||||
}
|
||||
d.writeBlob(rw, path, content)
|
||||
|
||||
default:
|
||||
// Check if this is a plugin script
|
||||
if script, ok := d.pluginScripts[path]; ok {
|
||||
d.writeBlob(rw, path, []byte(script))
|
||||
return
|
||||
}
|
||||
d.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) {
|
||||
htmlNode, err := getHTMLNode(indexHTML)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.appendSpinnerToBody {
|
||||
err = appendSpinnerToBody(htmlNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := insertScriptInHead(htmlNode, runtimeJSPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := insertScriptInHead(htmlNode, ipcJSPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Inject plugins
|
||||
for scriptName := range d.pluginScripts {
|
||||
if err := insertScriptInHead(htmlNode, scriptName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = html.Render(&buffer, htmlNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (d *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
|
||||
err := serveFile(rw, filename, blob)
|
||||
if err != nil {
|
||||
d.serveError(rw, err, "Unable to write content %s", filename)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) {
|
||||
args = append(args, err)
|
||||
d.logError(msg+": %s", args...)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (d *AssetServer) logDebug(message string, args ...interface{}) {
|
||||
if d.logger != nil {
|
||||
d.logger.Debug("[AssetServer] "+message, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *AssetServer) logError(message string, args ...interface{}) {
|
||||
if d.logger != nil {
|
||||
d.logger.Error("[AssetServer] "+message, args...)
|
||||
}
|
||||
}
|
31
v3/pkg/assetserver/assetserver_dev.go
Normal file
31
v3/pkg/assetserver/assetserver_dev.go
Normal file
@ -0,0 +1,31 @@
|
||||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
The assetserver for the dev mode.
|
||||
Depending on the UserAgent it injects a websocket based IPC script into `index.html` or the default desktop IPC. The
|
||||
default desktop IPC is injected when the webview accesses the devserver.
|
||||
*/
|
||||
func NewDevAssetServer(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
|
||||
result, err := NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.appendSpinnerToBody = true
|
||||
result.ipcJS = func(req *http.Request) []byte {
|
||||
if strings.Contains(req.UserAgent(), WailsUserAgentValue) {
|
||||
return runtime.DesktopIPC()
|
||||
}
|
||||
return runtime.WebsocketIPC()
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
78
v3/pkg/assetserver/assetserver_legacy.go
Normal file
78
v3/pkg/assetserver/assetserver_legacy.go
Normal file
@ -0,0 +1,78 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
|
||||
)
|
||||
|
||||
// ProcessHTTPRequest processes the HTTP Request by faking a golang HTTP Server.
|
||||
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||
func (d *AssetServer) ProcessHTTPRequestLegacy(rw http.ResponseWriter, reqGetter func() (*http.Request, error)) {
|
||||
d.processWebViewRequest(&legacyRequest{reqGetter: reqGetter, rw: rw})
|
||||
}
|
||||
|
||||
type legacyRequest struct {
|
||||
req *http.Request
|
||||
rw http.ResponseWriter
|
||||
|
||||
reqGetter func() (*http.Request, error)
|
||||
}
|
||||
|
||||
func (r *legacyRequest) URL() (string, error) {
|
||||
req, err := r.request()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return req.URL.String(), nil
|
||||
}
|
||||
|
||||
func (r *legacyRequest) Method() (string, error) {
|
||||
req, err := r.request()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return req.Method, nil
|
||||
}
|
||||
|
||||
func (r *legacyRequest) Header() (http.Header, error) {
|
||||
req, err := r.request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req.Header, nil
|
||||
}
|
||||
|
||||
func (r *legacyRequest) Body() (io.ReadCloser, error) {
|
||||
req, err := r.request()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req.Body, nil
|
||||
}
|
||||
|
||||
func (r legacyRequest) Response() webview.ResponseWriter {
|
||||
return &legacyRequestNoOpCloserResponseWriter{r.rw}
|
||||
}
|
||||
|
||||
func (r legacyRequest) Close() error { return nil }
|
||||
|
||||
func (r *legacyRequest) request() (*http.Request, error) {
|
||||
if r.req != nil {
|
||||
return r.req, nil
|
||||
}
|
||||
|
||||
req, err := r.reqGetter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.req = req
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type legacyRequestNoOpCloserResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() {}
|
161
v3/pkg/assetserver/assetserver_webview.go
Normal file
161
v3/pkg/assetserver/assetserver_webview.go
Normal file
@ -0,0 +1,161 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
|
||||
)
|
||||
|
||||
type assetServerWebView struct {
|
||||
// ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed.
|
||||
ExpectedWebViewHost string
|
||||
|
||||
dispatchInit sync.Once
|
||||
dispatchReqC chan<- webview.Request
|
||||
dispatchWorkers int
|
||||
}
|
||||
|
||||
// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
|
||||
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
|
||||
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
d.dispatchInit.Do(func() {
|
||||
workers := d.dispatchWorkers
|
||||
if workers == 0 {
|
||||
workers = 10
|
||||
}
|
||||
|
||||
workerC := make(chan webview.Request, workers*2)
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for req := range workerC {
|
||||
uri, _ := req.URL()
|
||||
d.processWebViewRequest(req)
|
||||
if err := req.Close(); err != nil {
|
||||
d.logError("Unable to call close for request for uri '%s'", uri)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
dispatchC := make(chan webview.Request)
|
||||
go queueingDispatcher(50, dispatchC, workerC)
|
||||
|
||||
d.dispatchReqC = dispatchC
|
||||
})
|
||||
|
||||
d.dispatchReqC <- req
|
||||
}
|
||||
|
||||
// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server.
|
||||
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||
func (d *AssetServer) processWebViewRequest(r webview.Request) {
|
||||
wrw := r.Response()
|
||||
defer wrw.Finish()
|
||||
|
||||
var rw http.ResponseWriter = &contentTypeSniffer{rw: wrw} // Make sure we have a Content-Type sniffer
|
||||
defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status
|
||||
|
||||
uri, err := r.URL()
|
||||
if err != nil {
|
||||
d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
method, err := r.Method()
|
||||
if err != nil {
|
||||
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
header, err := r.Header()
|
||||
if err != nil {
|
||||
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := r.Body()
|
||||
if err != nil {
|
||||
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if body == nil {
|
||||
body = http.NoBody
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
req, err := http.NewRequest(method, uri, body)
|
||||
if err != nil {
|
||||
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
|
||||
return
|
||||
}
|
||||
req.Header = header
|
||||
|
||||
if req.RemoteAddr == "" {
|
||||
// 192.0.2.0/24 is "TEST-NET" in RFC 5737
|
||||
req.RemoteAddr = "192.0.2.1:1234"
|
||||
}
|
||||
|
||||
if req.RequestURI == "" && req.URL != nil {
|
||||
req.RequestURI = req.URL.String()
|
||||
}
|
||||
|
||||
if req.ContentLength == 0 {
|
||||
req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64)
|
||||
} else {
|
||||
req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength))
|
||||
}
|
||||
|
||||
if host := req.Header.Get(HeaderHost); host != "" {
|
||||
req.Host = host
|
||||
}
|
||||
|
||||
if expectedHost := d.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host {
|
||||
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host))
|
||||
return
|
||||
}
|
||||
|
||||
d.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (d *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) {
|
||||
logInfo := uri
|
||||
if uri, err := url.ParseRequestURI(uri); err == nil {
|
||||
logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1)
|
||||
}
|
||||
|
||||
d.logError("Error processing request '%s': %s (HttpResponse=500)", logInfo, err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) {
|
||||
q := newRingqueue[T](minQueueSize)
|
||||
for {
|
||||
in, ok := <-inC
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
q.Add(in)
|
||||
for q.Len() != 0 {
|
||||
out, _ := q.Peek()
|
||||
select {
|
||||
case outC <- out:
|
||||
q.Remove()
|
||||
case in, ok := <-inC:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
q.Add(in)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
115
v3/pkg/assetserver/common.go
Normal file
115
v3/pkg/assetserver/common.go
Normal file
@ -0,0 +1,115 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderHost = "Host"
|
||||
HeaderContentType = "Content-Type"
|
||||
HeaderContentLength = "Content-Length"
|
||||
HeaderUserAgent = "User-Agent"
|
||||
HeaderCacheControl = "Cache-Control"
|
||||
HeaderUpgrade = "Upgrade"
|
||||
|
||||
WailsUserAgentValue = "wails.io"
|
||||
)
|
||||
|
||||
func serveFile(rw http.ResponseWriter, filename string, blob []byte) error {
|
||||
header := rw.Header()
|
||||
header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob)))
|
||||
if mimeType := header.Get(HeaderContentType); mimeType == "" {
|
||||
mimeType = GetMimetype(filename, blob)
|
||||
header.Set(HeaderContentType, mimeType)
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, err := io.Copy(rw, bytes.NewReader(blob))
|
||||
return err
|
||||
}
|
||||
|
||||
func createScriptNode(scriptName string) *html.Node {
|
||||
return &html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: "script",
|
||||
Attr: []html.Attribute{
|
||||
{
|
||||
Key: "src",
|
||||
Val: scriptName,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createDivNode(id string) *html.Node {
|
||||
return &html.Node{
|
||||
Type: html.ElementNode,
|
||||
Data: "div",
|
||||
Attr: []html.Attribute{
|
||||
{
|
||||
Namespace: "",
|
||||
Key: "id",
|
||||
Val: id,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func insertScriptInHead(htmlNode *html.Node, scriptName string) error {
|
||||
headNode := findFirstTag(htmlNode, "head")
|
||||
if headNode == nil {
|
||||
return errors.New("cannot find head in HTML")
|
||||
}
|
||||
scriptNode := createScriptNode(scriptName)
|
||||
if headNode.FirstChild != nil {
|
||||
headNode.InsertBefore(scriptNode, headNode.FirstChild)
|
||||
} else {
|
||||
headNode.AppendChild(scriptNode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendSpinnerToBody(htmlNode *html.Node) error {
|
||||
bodyNode := findFirstTag(htmlNode, "body")
|
||||
if bodyNode == nil {
|
||||
return errors.New("cannot find body in HTML")
|
||||
}
|
||||
scriptNode := createDivNode("wails-spinner")
|
||||
bodyNode.AppendChild(scriptNode)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHTMLNode(htmldata []byte) (*html.Node, error) {
|
||||
return html.Parse(bytes.NewReader(htmldata))
|
||||
}
|
||||
|
||||
func findFirstTag(htmlnode *html.Node, tagName string) *html.Node {
|
||||
var extractor func(*html.Node) *html.Node
|
||||
var result *html.Node
|
||||
extractor = func(node *html.Node) *html.Node {
|
||||
if node.Type == html.ElementNode && node.Data == tagName {
|
||||
return node
|
||||
}
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
result := extractor(child)
|
||||
if result != nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result = extractor(htmlnode)
|
||||
return result
|
||||
}
|
||||
|
||||
func isWebSocket(req *http.Request) bool {
|
||||
upgrade := req.Header.Get(HeaderUpgrade)
|
||||
return strings.EqualFold(upgrade, "websocket")
|
||||
}
|
42
v3/pkg/assetserver/content_type_sniffer.go
Normal file
42
v3/pkg/assetserver/content_type_sniffer.go
Normal file
@ -0,0 +1,42 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type contentTypeSniffer struct {
|
||||
rw http.ResponseWriter
|
||||
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) Header() http.Header {
|
||||
return rw.rw.Header()
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) Write(buf []byte) (int, error) {
|
||||
rw.writeHeader(buf)
|
||||
return rw.rw.Write(buf)
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) WriteHeader(code int) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
rw.rw.WriteHeader(code)
|
||||
rw.wroteHeader = true
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) writeHeader(b []byte) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
m := rw.rw.Header()
|
||||
if _, hasType := m[HeaderContentType]; !hasType {
|
||||
m.Set(HeaderContentType, http.DetectContentType(b))
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
39
v3/pkg/assetserver/defaultindex.html
Normal file
39
v3/pkg/assetserver/defaultindex.html
Normal file
File diff suppressed because one or more lines are too long
75
v3/pkg/assetserver/fs.go
Normal file
75
v3/pkg/assetserver/fs.go
Normal file
@ -0,0 +1,75 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files.
|
||||
func FindEmbedRootPath(fsys embed.FS) (string, error) {
|
||||
stopErr := fmt.Errorf("files or multiple dirs found")
|
||||
|
||||
fPath := ""
|
||||
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
fPath = path
|
||||
if entries, dErr := fs.ReadDir(fsys, path); dErr != nil {
|
||||
return dErr
|
||||
} else if len(entries) <= 1 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return stopErr
|
||||
})
|
||||
|
||||
if err != nil && err != stopErr {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fPath, nil
|
||||
}
|
||||
|
||||
func FindPathToFile(fsys fs.FS, file string) (string, error) {
|
||||
stat, _ := fs.Stat(fsys, file)
|
||||
if stat != nil {
|
||||
return ".", nil
|
||||
}
|
||||
var indexFiles []string
|
||||
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.HasSuffix(path, file) {
|
||||
indexFiles = append(indexFiles, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(indexFiles) > 1 {
|
||||
selected := indexFiles[0]
|
||||
for _, f := range indexFiles {
|
||||
if len(f) < len(selected) {
|
||||
selected = f
|
||||
}
|
||||
}
|
||||
path, _ := filepath.Split(selected)
|
||||
return path, nil
|
||||
}
|
||||
if len(indexFiles) > 0 {
|
||||
path, _ := filepath.Split(indexFiles[0])
|
||||
return path, nil
|
||||
}
|
||||
return "", fmt.Errorf("%s: %w", file, os.ErrNotExist)
|
||||
}
|
20
v3/pkg/assetserver/middleware.go
Normal file
20
v3/pkg/assetserver/middleware.go
Normal file
@ -0,0 +1,20 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Middleware defines a HTTP middleware that can be applied to the AssetServer.
|
||||
// The handler passed as next is the next handler in the chain. One can decide to call the next handler
|
||||
// or implement a specialized handling.
|
||||
type Middleware func(next http.Handler) http.Handler
|
||||
|
||||
// ChainMiddleware allows chaining multiple middlewares to one middleware.
|
||||
func ChainMiddleware(middleware ...Middleware) Middleware {
|
||||
return func(h http.Handler) http.Handler {
|
||||
for i := len(middleware) - 1; i >= 0; i-- {
|
||||
h = middleware[i](h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
}
|
67
v3/pkg/assetserver/mimecache.go
Normal file
67
v3/pkg/assetserver/mimecache.go
Normal file
@ -0,0 +1,67 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/mimetype"
|
||||
)
|
||||
|
||||
var (
|
||||
mimeCache = map[string]string{}
|
||||
mimeMutex sync.Mutex
|
||||
|
||||
// The list of builtin mime-types by extension as defined by
|
||||
// the golang standard lib package "mime"
|
||||
// The standard lib also takes into account mime type definitions from
|
||||
// etc files like '/etc/apache2/mime.types' but we want to have the
|
||||
// same behavivour on all platforms and not depend on some external file.
|
||||
mimeTypesByExt = map[string]string{
|
||||
".avif": "image/avif",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".gif": "image/gif",
|
||||
".htm": "text/html; charset=utf-8",
|
||||
".html": "text/html; charset=utf-8",
|
||||
".jpeg": "image/jpeg",
|
||||
".jpg": "image/jpeg",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".json": "application/json",
|
||||
".mjs": "text/javascript; charset=utf-8",
|
||||
".pdf": "application/pdf",
|
||||
".png": "image/png",
|
||||
".svg": "image/svg+xml",
|
||||
".wasm": "application/wasm",
|
||||
".webp": "image/webp",
|
||||
".xml": "text/xml; charset=utf-8",
|
||||
}
|
||||
)
|
||||
|
||||
func GetMimetype(filename string, data []byte) string {
|
||||
mimeMutex.Lock()
|
||||
defer mimeMutex.Unlock()
|
||||
|
||||
result := mimeTypesByExt[filepath.Ext(filename)]
|
||||
if result != "" {
|
||||
return result
|
||||
}
|
||||
|
||||
result = mimeCache[filename]
|
||||
if result != "" {
|
||||
return result
|
||||
}
|
||||
|
||||
detect := mimetype.Detect(data)
|
||||
if detect == nil {
|
||||
result = http.DetectContentType(data)
|
||||
} else {
|
||||
result = detect.String()
|
||||
}
|
||||
|
||||
if result == "" {
|
||||
result = "application/octet-stream"
|
||||
}
|
||||
|
||||
mimeCache[filename] = result
|
||||
return result
|
||||
}
|
46
v3/pkg/assetserver/mimecache_test.go
Normal file
46
v3/pkg/assetserver/mimecache_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetMimetype(t *testing.T) {
|
||||
type args struct {
|
||||
filename string
|
||||
data []byte
|
||||
}
|
||||
bomUTF8 := []byte{0xef, 0xbb, 0xbf}
|
||||
var emptyMsg []byte
|
||||
css := []byte("body{margin:0;padding:0;background-color:#d579b2}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;background-color:#ededed}#nav{padding:30px}#nav a{font-weight:700;color:#2c\n3e50}#nav a.router-link-exact-active{color:#42b983}.hello[data-v-4e26ad49]{margin:10px 0}")
|
||||
html := []byte("<!DOCTYPE html><html><head>title</head><body></body></html>")
|
||||
bomHtml := append(bomUTF8, html...)
|
||||
svg := []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M15.707 14.293l-4.822-4.822a6.019 6.019 0 1 0-1.414 1.414l4.822 4.822a1 1 0 0 0 1.414-1.414zM6 10a4 4 0 1 1 4-4 4 4 0 0 1-4 4z\"></path></svg>")
|
||||
svgWithComment := append([]byte("<!-- this is a comment -->"), svg...)
|
||||
svgWithCommentAndControlChars := append([]byte(" \r\n "), svgWithComment...)
|
||||
svgWithBomCommentAndControlChars := append(bomUTF8, append([]byte(" \r\n "), svgWithComment...)...)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{"nil data", args{"nil.svg", nil}, "image/svg+xml"},
|
||||
{"empty data", args{"empty.html", emptyMsg}, "text/html; charset=utf-8"},
|
||||
{"css", args{"test.css", css}, "text/css; charset=utf-8"},
|
||||
{"js", args{"test.js", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"},
|
||||
{"mjs", args{"test.mjs", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"},
|
||||
{"html-utf8", args{"test_utf8.html", html}, "text/html; charset=utf-8"},
|
||||
{"html-bom-utf8", args{"test_bom_utf8.html", bomHtml}, "text/html; charset=utf-8"},
|
||||
{"svg", args{"test.svg", svg}, "image/svg+xml"},
|
||||
{"svg-w-comment", args{"test_comment.svg", svgWithComment}, "image/svg+xml"},
|
||||
{"svg-w-control-comment", args{"test_control_comment.svg", svgWithCommentAndControlChars}, "image/svg+xml"},
|
||||
{"svg-w-bom-control-comment", args{"test_bom_control_comment.svg", svgWithBomCommentAndControlChars}, "image/svg+xml"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetMimetype(tt.args.filename, tt.args.data); got != tt.want {
|
||||
t.Errorf("GetMimetype() = '%v', want '%v'", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
45
v3/pkg/assetserver/options.go
Normal file
45
v3/pkg/assetserver/options.go
Normal file
@ -0,0 +1,45 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Options defines the configuration of the AssetServer.
|
||||
type Options struct {
|
||||
// Assets defines the static assets to be used. A GET request is first tried to be served from this Assets. If the Assets returns
|
||||
// `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET
|
||||
// request from it.
|
||||
//
|
||||
// If set to nil, all GET requests will be forwarded to Handler.
|
||||
Assets fs.FS
|
||||
|
||||
// Handler will be called for every GET request that can't be served from Assets, due to `os.ErrNotExist`. Furthermore all
|
||||
// non GET requests will always be served from this Handler.
|
||||
//
|
||||
// If not defined, the result is the following in cases where the Handler would have been called:
|
||||
// GET request: `http.StatusNotFound`
|
||||
// Other request: `http.StatusMethodNotAllowed`
|
||||
Handler http.Handler
|
||||
|
||||
// Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default
|
||||
// request handler dynamically, e.g. implement specialized Routing etc.
|
||||
// The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default
|
||||
// handler used by the AssetServer as an argument.
|
||||
//
|
||||
// If not defined, the default AssetServer request chain is executed.
|
||||
//
|
||||
// Multiple Middlewares can be chained together with:
|
||||
// ChainMiddleware(middleware ...Middleware) Middleware
|
||||
Middleware Middleware
|
||||
}
|
||||
|
||||
// Validate the options
|
||||
func (o Options) Validate() error {
|
||||
if o.Assets == nil && o.Handler == nil && o.Middleware == nil {
|
||||
return fmt.Errorf("AssetServer options invalid: either Assets, Handler or Middleware must be set")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
101
v3/pkg/assetserver/ringqueue.go
Normal file
101
v3/pkg/assetserver/ringqueue.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Code from https://github.com/erikdubbelboer/ringqueue
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Erik Dubbelboer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
package assetserver
|
||||
|
||||
type ringqueue[T any] struct {
|
||||
nodes []T
|
||||
head int
|
||||
tail int
|
||||
cnt int
|
||||
|
||||
minSize int
|
||||
}
|
||||
|
||||
func newRingqueue[T any](minSize uint) *ringqueue[T] {
|
||||
if minSize < 2 {
|
||||
minSize = 2
|
||||
}
|
||||
return &ringqueue[T]{
|
||||
nodes: make([]T, minSize),
|
||||
minSize: int(minSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *ringqueue[T]) resize(n int) {
|
||||
nodes := make([]T, n)
|
||||
if q.head < q.tail {
|
||||
copy(nodes, q.nodes[q.head:q.tail])
|
||||
} else {
|
||||
copy(nodes, q.nodes[q.head:])
|
||||
copy(nodes[len(q.nodes)-q.head:], q.nodes[:q.tail])
|
||||
}
|
||||
|
||||
q.tail = q.cnt % n
|
||||
q.head = 0
|
||||
q.nodes = nodes
|
||||
}
|
||||
|
||||
func (q *ringqueue[T]) Add(i T) {
|
||||
if q.cnt == len(q.nodes) {
|
||||
// Also tested a grow rate of 1.5, see: http://stackoverflow.com/questions/2269063/buffer-growth-strategy
|
||||
// In Go this resulted in a higher memory usage.
|
||||
q.resize(q.cnt * 2)
|
||||
}
|
||||
q.nodes[q.tail] = i
|
||||
q.tail = (q.tail + 1) % len(q.nodes)
|
||||
q.cnt++
|
||||
}
|
||||
|
||||
func (q *ringqueue[T]) Peek() (T, bool) {
|
||||
if q.cnt == 0 {
|
||||
var none T
|
||||
return none, false
|
||||
}
|
||||
return q.nodes[q.head], true
|
||||
}
|
||||
|
||||
func (q *ringqueue[T]) Remove() (T, bool) {
|
||||
if q.cnt == 0 {
|
||||
var none T
|
||||
return none, false
|
||||
}
|
||||
i := q.nodes[q.head]
|
||||
q.head = (q.head + 1) % len(q.nodes)
|
||||
q.cnt--
|
||||
|
||||
if n := len(q.nodes) / 2; n > q.minSize && q.cnt <= n {
|
||||
q.resize(n)
|
||||
}
|
||||
|
||||
return i, true
|
||||
}
|
||||
|
||||
func (q *ringqueue[T]) Cap() int {
|
||||
return cap(q.nodes)
|
||||
}
|
||||
|
||||
func (q *ringqueue[T]) Len() int {
|
||||
return q.cnt
|
||||
}
|
8
v3/pkg/assetserver/testdata/index.html
vendored
Normal file
8
v3/pkg/assetserver/testdata/index.html
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="/main.css" rel="stylesheet">
|
||||
</head>
|
||||
<body data-wails-drag>
|
||||
<div id="logo"></div>
|
||||
</body>
|
||||
</html>
|
39
v3/pkg/assetserver/testdata/main.css
vendored
Normal file
39
v3/pkg/assetserver/testdata/main.css
vendored
Normal file
File diff suppressed because one or more lines are too long
20
v3/pkg/assetserver/testdata/main.js
vendored
Normal file
20
v3/pkg/assetserver/testdata/main.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import {ready} from '@wails/runtime';
|
||||
|
||||
ready(() => {
|
||||
// Get input + focus
|
||||
let nameElement = document.getElementById("name");
|
||||
nameElement.focus();
|
||||
|
||||
// Setup the greet function
|
||||
window.greet = function () {
|
||||
|
||||
// Get name
|
||||
let name = nameElement.value;
|
||||
|
||||
// Call App.Greet(name)
|
||||
window.backend.main.App.Greet(name).then((result) => {
|
||||
// Update result with data back from App.Greet()
|
||||
document.getElementById("result").innerText = result;
|
||||
});
|
||||
};
|
||||
});
|
8
v3/pkg/assetserver/testdata/subdir/index.html
vendored
Normal file
8
v3/pkg/assetserver/testdata/subdir/index.html
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="/main.css" rel="stylesheet">
|
||||
</head>
|
||||
<body data-wails-drag>
|
||||
<div id="logo"></div>
|
||||
</body>
|
||||
</html>
|
39
v3/pkg/assetserver/testdata/subdir/main.css
vendored
Normal file
39
v3/pkg/assetserver/testdata/subdir/main.css
vendored
Normal file
File diff suppressed because one or more lines are too long
20
v3/pkg/assetserver/testdata/subdir/main.js
vendored
Normal file
20
v3/pkg/assetserver/testdata/subdir/main.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import {ready} from '@wails/runtime';
|
||||
|
||||
ready(() => {
|
||||
// Get input + focus
|
||||
let nameElement = document.getElementById("name");
|
||||
nameElement.focus();
|
||||
|
||||
// Setup the greet function
|
||||
window.greet = function () {
|
||||
|
||||
// Get name
|
||||
let name = nameElement.value;
|
||||
|
||||
// Call App.Greet(name)
|
||||
window.backend.main.App.Greet(name).then((result) => {
|
||||
// Update result with data back from App.Greet()
|
||||
document.getElementById("result").innerText = result;
|
||||
});
|
||||
};
|
||||
});
|
6
v3/pkg/assetserver/testdata/testdata.go
vendored
Normal file
6
v3/pkg/assetserver/testdata/testdata.go
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package testdata
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed index.html main.css main.js
|
||||
var TopLevelFS embed.FS
|
17
v3/pkg/assetserver/webview/request.go
Normal file
17
v3/pkg/assetserver/webview/request.go
Normal file
@ -0,0 +1,17 @@
|
||||
package webview
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Request interface {
|
||||
URL() (string, error)
|
||||
Method() (string, error)
|
||||
Header() (http.Header, error)
|
||||
Body() (io.ReadCloser, error)
|
||||
|
||||
Response() ResponseWriter
|
||||
|
||||
Close() error
|
||||
}
|
248
v3/pkg/assetserver/webview/request_darwin.go
Normal file
248
v3/pkg/assetserver/webview/request_darwin.go
Normal file
@ -0,0 +1,248 @@
|
||||
//go:build darwin
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework WebKit
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#include <string.h>
|
||||
|
||||
static void URLSchemeTaskRetain(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
[urlSchemeTask retain];
|
||||
}
|
||||
|
||||
static void URLSchemeTaskRelease(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
[urlSchemeTask release];
|
||||
}
|
||||
|
||||
static const char * URLSchemeTaskRequestURL(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
return [urlSchemeTask.request.URL.absoluteString UTF8String];
|
||||
}
|
||||
}
|
||||
|
||||
static const char * URLSchemeTaskRequestMethod(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
return [urlSchemeTask.request.HTTPMethod UTF8String];
|
||||
}
|
||||
}
|
||||
|
||||
static const char * URLSchemeTaskRequestHeadersJSON(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
NSData *headerData = [NSJSONSerialization dataWithJSONObject: urlSchemeTask.request.allHTTPHeaderFields options:0 error: nil];
|
||||
if (!headerData) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString* headerString = [[[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding] autorelease];
|
||||
const char * headerJSON = [headerString UTF8String];
|
||||
|
||||
return strdup(headerJSON);
|
||||
}
|
||||
}
|
||||
|
||||
static bool URLSchemeTaskRequestBodyBytes(void *wkUrlSchemeTask, const void **body, int *bodyLen) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
if (!urlSchemeTask.request.HTTPBody) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*body = urlSchemeTask.request.HTTPBody.bytes;
|
||||
*bodyLen = urlSchemeTask.request.HTTPBody.length;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool URLSchemeTaskRequestBodyStreamOpen(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
if (!urlSchemeTask.request.HTTPBodyStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[urlSchemeTask.request.HTTPBodyStream open];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void URLSchemeTaskRequestBodyStreamClose(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
if (!urlSchemeTask.request.HTTPBodyStream) {
|
||||
return;
|
||||
}
|
||||
|
||||
[urlSchemeTask.request.HTTPBodyStream close];
|
||||
}
|
||||
}
|
||||
|
||||
static int URLSchemeTaskRequestBodyStreamRead(void *wkUrlSchemeTask, void *buf, int bufLen) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
|
||||
@autoreleasepool {
|
||||
NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
|
||||
if (!stream) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
NSStreamStatus status = stream.streamStatus;
|
||||
if (status == NSStreamStatusAtEnd || !stream.hasBytesAvailable) {
|
||||
return 0;
|
||||
} else if (status != NSStreamStatusOpen) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
return [stream read:buf maxLength:bufLen];
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest based on a pointer to an `id<WKURLSchemeTask>`
|
||||
func NewRequest(wkURLSchemeTask unsafe.Pointer) Request {
|
||||
C.URLSchemeTaskRetain(wkURLSchemeTask)
|
||||
return newRequestFinalizer(&request{task: wkURLSchemeTask})
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
|
||||
type request struct {
|
||||
task unsafe.Pointer
|
||||
|
||||
header http.Header
|
||||
body io.ReadCloser
|
||||
rw *responseWriter
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil
|
||||
}
|
||||
|
||||
func (r *request) Method() (string, error) {
|
||||
return C.GoString(C.URLSchemeTaskRequestMethod(r.task)), nil
|
||||
}
|
||||
|
||||
func (r *request) Header() (http.Header, error) {
|
||||
if r.header != nil {
|
||||
return r.header, nil
|
||||
}
|
||||
|
||||
header := http.Header{}
|
||||
if cHeaders := C.URLSchemeTaskRequestHeadersJSON(r.task); cHeaders != nil {
|
||||
if headers := C.GoString(cHeaders); headers != "" {
|
||||
var h map[string]string
|
||||
if err := json.Unmarshal([]byte(headers), &h); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal request headers: %s", err)
|
||||
}
|
||||
|
||||
for k, v := range h {
|
||||
header.Add(k, v)
|
||||
}
|
||||
}
|
||||
C.free(unsafe.Pointer(cHeaders))
|
||||
}
|
||||
r.header = header
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func (r *request) Body() (io.ReadCloser, error) {
|
||||
if r.body != nil {
|
||||
return r.body, nil
|
||||
}
|
||||
|
||||
var body unsafe.Pointer
|
||||
var bodyLen C.int
|
||||
if C.URLSchemeTaskRequestBodyBytes(r.task, &body, &bodyLen) {
|
||||
if body != nil && bodyLen > 0 {
|
||||
r.body = io.NopCloser(bytes.NewReader(C.GoBytes(body, bodyLen)))
|
||||
} else {
|
||||
r.body = http.NoBody
|
||||
}
|
||||
} else if C.URLSchemeTaskRequestBodyStreamOpen(r.task) {
|
||||
r.body = &requestBodyStreamReader{task: r.task}
|
||||
}
|
||||
|
||||
return r.body, nil
|
||||
}
|
||||
|
||||
func (r *request) Response() ResponseWriter {
|
||||
if r.rw != nil {
|
||||
return r.rw
|
||||
}
|
||||
|
||||
r.rw = &responseWriter{r: r}
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var err error
|
||||
if r.body != nil {
|
||||
err = r.body.Close()
|
||||
}
|
||||
r.Response().Finish()
|
||||
C.URLSchemeTaskRelease(r.task)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = &requestBodyStreamReader{}
|
||||
|
||||
type requestBodyStreamReader struct {
|
||||
task unsafe.Pointer
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Read implements io.Reader
|
||||
func (r *requestBodyStreamReader) Read(p []byte) (n int, err error) {
|
||||
var content unsafe.Pointer
|
||||
var contentLen int
|
||||
if p != nil {
|
||||
content = unsafe.Pointer(&p[0])
|
||||
contentLen = len(p)
|
||||
}
|
||||
|
||||
res := C.URLSchemeTaskRequestBodyStreamRead(r.task, content, C.int(contentLen))
|
||||
if res > 0 {
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
switch res {
|
||||
case 0:
|
||||
return 0, io.EOF
|
||||
case -1:
|
||||
return 0, fmt.Errorf("body: stream error")
|
||||
case -2:
|
||||
return 0, fmt.Errorf("body: no stream defined")
|
||||
case -3:
|
||||
return 0, io.ErrClosedPipe
|
||||
default:
|
||||
return 0, fmt.Errorf("body: unknown error %d", res)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *requestBodyStreamReader) Close() error {
|
||||
if r.closed {
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
|
||||
C.URLSchemeTaskRequestBodyStreamClose(r.task)
|
||||
return nil
|
||||
}
|
40
v3/pkg/assetserver/webview/request_finalizer.go
Normal file
40
v3/pkg/assetserver/webview/request_finalizer.go
Normal file
@ -0,0 +1,40 @@
|
||||
package webview
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var _ Request = &requestFinalizer{}
|
||||
|
||||
type requestFinalizer struct {
|
||||
Request
|
||||
closed int32
|
||||
}
|
||||
|
||||
// newRequestFinalizer returns a request with a runtime finalizer to make sure it will be closed from the finalizer
|
||||
// if it has not been already closed.
|
||||
// It also makes sure Close() of the wrapping request is only called once.
|
||||
func newRequestFinalizer(r Request) Request {
|
||||
rf := &requestFinalizer{Request: r}
|
||||
// Make sure to async release since it might block the finalizer goroutine for a longer period
|
||||
runtime.SetFinalizer(rf, func(obj *requestFinalizer) { rf.close(true) })
|
||||
return rf
|
||||
}
|
||||
|
||||
func (r *requestFinalizer) Close() error {
|
||||
return r.close(false)
|
||||
}
|
||||
|
||||
func (r *requestFinalizer) close(asyncRelease bool) error {
|
||||
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
||||
runtime.SetFinalizer(r, nil)
|
||||
if asyncRelease {
|
||||
go r.Request.Close()
|
||||
return nil
|
||||
} else {
|
||||
return r.Request.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
83
v3/pkg/assetserver/webview/request_linux.go
Normal file
83
v3/pkg/assetserver/webview/request_linux.go
Normal file
@ -0,0 +1,83 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest`
|
||||
func NewRequest(webKitURISchemeRequest unsafe.Pointer) Request {
|
||||
webkitReq := (*C.WebKitURISchemeRequest)(webKitURISchemeRequest)
|
||||
C.g_object_ref(C.gpointer(webkitReq))
|
||||
|
||||
req := &request{req: webkitReq}
|
||||
return newRequestFinalizer(req)
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
|
||||
type request struct {
|
||||
req *C.WebKitURISchemeRequest
|
||||
|
||||
header http.Header
|
||||
body io.ReadCloser
|
||||
rw *responseWriter
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
return C.GoString(C.webkit_uri_scheme_request_get_uri(r.req)), nil
|
||||
}
|
||||
|
||||
func (r *request) Method() (string, error) {
|
||||
return webkit_uri_scheme_request_get_http_method(r.req), nil
|
||||
}
|
||||
|
||||
func (r *request) Header() (http.Header, error) {
|
||||
if r.header != nil {
|
||||
return r.header, nil
|
||||
}
|
||||
|
||||
r.header = webkit_uri_scheme_request_get_http_headers(r.req)
|
||||
return r.header, nil
|
||||
}
|
||||
|
||||
func (r *request) Body() (io.ReadCloser, error) {
|
||||
if r.body != nil {
|
||||
return r.body, nil
|
||||
}
|
||||
|
||||
r.body = webkit_uri_scheme_request_get_http_body(r.req)
|
||||
|
||||
return r.body, nil
|
||||
}
|
||||
|
||||
func (r *request) Response() ResponseWriter {
|
||||
if r.rw != nil {
|
||||
return r.rw
|
||||
}
|
||||
|
||||
r.rw = &responseWriter{req: r.req}
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var err error
|
||||
if r.body != nil {
|
||||
err = r.body.Close()
|
||||
}
|
||||
r.Response().Finish()
|
||||
C.g_object_unref(C.gpointer(r.req))
|
||||
return err
|
||||
}
|
94
v3/pkg/assetserver/webview/request_linux_purego.go
Normal file
94
v3/pkg/assetserver/webview/request_linux_purego.go
Normal file
@ -0,0 +1,94 @@
|
||||
//go:build linux && purego
|
||||
// +build linux,purego
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest`
|
||||
//
|
||||
// Please make sure to call Release() when finished using the request.
|
||||
func NewRequest(webKitURISchemeRequest uintptr) Request {
|
||||
webkitReq := webKitURISchemeRequest
|
||||
req := &request{req: webkitReq}
|
||||
req.AddRef()
|
||||
return req
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
|
||||
type request struct {
|
||||
req uintptr
|
||||
|
||||
header http.Header
|
||||
body io.ReadCloser
|
||||
rw *responseWriter
|
||||
}
|
||||
|
||||
func (r *request) AddRef() error {
|
||||
var objectRef func(uintptr)
|
||||
purego.RegisterLibFunc(&objectRef, gtk, "g_object_ref")
|
||||
objectRef(r.req)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) Release() error {
|
||||
var objectUnref func(uintptr)
|
||||
purego.RegisterLibFunc(&objectUnref, gtk, "g_object_unref")
|
||||
objectUnref(r.req)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
var getUri func(uintptr) string
|
||||
purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_uri")
|
||||
return getUri(r.req), nil
|
||||
}
|
||||
|
||||
func (r *request) Method() (string, error) {
|
||||
return webkit_uri_scheme_request_get_http_method(r.req), nil
|
||||
}
|
||||
|
||||
func (r *request) Header() (http.Header, error) {
|
||||
if r.header != nil {
|
||||
return r.header, nil
|
||||
}
|
||||
|
||||
r.header = webkit_uri_scheme_request_get_http_headers(r.req)
|
||||
return r.header, nil
|
||||
}
|
||||
|
||||
func (r *request) Body() (io.ReadCloser, error) {
|
||||
if r.body != nil {
|
||||
return r.body, nil
|
||||
}
|
||||
|
||||
// WebKit2GTK has currently no support for request bodies.
|
||||
r.body = http.NoBody
|
||||
|
||||
return r.body, nil
|
||||
}
|
||||
|
||||
func (r *request) Response() ResponseWriter {
|
||||
if r.rw != nil {
|
||||
return r.rw
|
||||
}
|
||||
|
||||
r.rw = &responseWriter{req: r.req}
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var err error
|
||||
if r.body != nil {
|
||||
err = r.body.Close()
|
||||
}
|
||||
r.Response().Finish()
|
||||
r.Release()
|
||||
return err
|
||||
}
|
25
v3/pkg/assetserver/webview/responsewriter.go
Normal file
25
v3/pkg/assetserver/webview/responsewriter.go
Normal file
@ -0,0 +1,25 @@
|
||||
package webview
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderContentLength = "Content-Length"
|
||||
HeaderContentType = "Content-Type"
|
||||
)
|
||||
|
||||
var (
|
||||
errRequestStopped = errors.New("request has been stopped")
|
||||
errResponseFinished = errors.New("response has been finished")
|
||||
)
|
||||
|
||||
// A ResponseWriter interface is used by an HTTP handler to
|
||||
// construct an HTTP response for the WebView.
|
||||
type ResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
|
||||
// Finish the response and flush all data. A Finish after the request has already been finished has no effect.
|
||||
Finish()
|
||||
}
|
147
v3/pkg/assetserver/webview/responsewriter_darwin.go
Normal file
147
v3/pkg/assetserver/webview/responsewriter_darwin.go
Normal file
@ -0,0 +1,147 @@
|
||||
//go:build darwin
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework WebKit
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
||||
|
||||
static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
if (urlSchemeTask == nil) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
@try {
|
||||
fn(urlSchemeTask);
|
||||
} @catch (NSException *exception) {
|
||||
// This is very bad to detect a stopped schemeTask this should be implemented in a better way
|
||||
// But it seems to be very tricky to not deadlock when keeping a lock curing executing fn()
|
||||
// It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want
|
||||
// to get the lock again to start another request or stop it.
|
||||
if ([exception.reason isEqualToString: @"This task has already been stopped"]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@throw exception;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool URLSchemeTaskDidReceiveData(void *wkUrlSchemeTask, void* data, int datalength) {
|
||||
return urlSchemeTaskCall(
|
||||
wkUrlSchemeTask,
|
||||
^(id<WKURLSchemeTask> urlSchemeTask) {
|
||||
NSData *nsdata = [NSData dataWithBytes:data length:datalength];
|
||||
[urlSchemeTask didReceiveData:nsdata];
|
||||
});
|
||||
}
|
||||
|
||||
static bool URLSchemeTaskDidFinish(void *wkUrlSchemeTask) {
|
||||
return urlSchemeTaskCall(
|
||||
wkUrlSchemeTask,
|
||||
^(id<WKURLSchemeTask> urlSchemeTask) {
|
||||
[urlSchemeTask didFinish];
|
||||
});
|
||||
}
|
||||
|
||||
static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCode, void *headersString, int headersStringLength) {
|
||||
return urlSchemeTaskCall(
|
||||
wkUrlSchemeTask,
|
||||
^(id<WKURLSchemeTask> urlSchemeTask) {
|
||||
NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength];
|
||||
NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData:nsHeadersJSON options: NSJSONReadingMutableContainers error: nil];
|
||||
NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease];
|
||||
|
||||
[urlSchemeTask didReceiveResponse:response];
|
||||
});
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
|
||||
type responseWriter struct {
|
||||
r *request
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
|
||||
finished bool
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Header() http.Header {
|
||||
if rw.header == nil {
|
||||
rw.header = http.Header{}
|
||||
}
|
||||
return rw.header
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(buf []byte) (int, error) {
|
||||
if rw.finished {
|
||||
return 0, errResponseFinished
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
var content unsafe.Pointer
|
||||
var contentLen int
|
||||
if buf != nil {
|
||||
content = unsafe.Pointer(&buf[0])
|
||||
contentLen = len(buf)
|
||||
}
|
||||
|
||||
if !C.URLSchemeTaskDidReceiveData(rw.r.task, content, C.int(contentLen)) {
|
||||
return 0, errRequestStopped
|
||||
}
|
||||
return contentLen, nil
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
if rw.wroteHeader || rw.finished {
|
||||
return
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
|
||||
header := map[string]string{}
|
||||
for k := range rw.Header() {
|
||||
header[k] = rw.Header().Get(k)
|
||||
}
|
||||
headerData, _ := json.Marshal(header)
|
||||
|
||||
var headers unsafe.Pointer
|
||||
var headersLen int
|
||||
if len(headerData) != 0 {
|
||||
headers = unsafe.Pointer(&headerData[0])
|
||||
headersLen = len(headerData)
|
||||
}
|
||||
|
||||
C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen))
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return
|
||||
}
|
||||
rw.finished = true
|
||||
|
||||
C.URLSchemeTaskDidFinish(rw.r.task)
|
||||
}
|
129
v3/pkg/assetserver/webview/responsewriter_linux.go
Normal file
129
v3/pkg/assetserver/webview/responsewriter_linux.go
Normal file
@ -0,0 +1,129 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
#include "gio/gunixinputstream.h"
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type responseWriter struct {
|
||||
req *C.WebKitURISchemeRequest
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
finished bool
|
||||
|
||||
w io.WriteCloser
|
||||
wErr error
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Header() http.Header {
|
||||
if rw.header == nil {
|
||||
rw.header = http.Header{}
|
||||
}
|
||||
return rw.header
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(buf []byte) (int, error) {
|
||||
if rw.finished {
|
||||
return 0, errResponseFinished
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if rw.wErr != nil {
|
||||
return 0, rw.wErr
|
||||
}
|
||||
return rw.w.Write(buf)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
if rw.wroteHeader || rw.finished {
|
||||
return
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
|
||||
contentLength := int64(-1)
|
||||
if sLen := rw.Header().Get(HeaderContentLength); sLen != "" {
|
||||
if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
|
||||
contentLength = pLen
|
||||
}
|
||||
}
|
||||
|
||||
// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
|
||||
// read FD is given to the InputStream and will be closed there.
|
||||
// Furthermore we especially don't want to have the FD_CLOEXEC
|
||||
rFD, w, err := pipe()
|
||||
if err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
|
||||
return
|
||||
}
|
||||
rw.w = w
|
||||
|
||||
stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1))
|
||||
defer C.g_object_unref(C.gpointer(stream))
|
||||
|
||||
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return
|
||||
}
|
||||
rw.finished = true
|
||||
if rw.w != nil {
|
||||
rw.w.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) finishWithError(code int, err error) {
|
||||
if rw.w != nil {
|
||||
rw.w.Close()
|
||||
rw.w = &nopCloser{io.Discard}
|
||||
}
|
||||
rw.wErr = err
|
||||
|
||||
msg := C.CString(err.Error())
|
||||
gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg)
|
||||
C.webkit_uri_scheme_request_finish_error(rw.req, gerr)
|
||||
C.g_error_free(gerr)
|
||||
C.free(unsafe.Pointer(msg))
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
func pipe() (r int, w *os.File, err error) {
|
||||
var p [2]int
|
||||
e := syscall.Pipe2(p[0:], 0)
|
||||
if e != nil {
|
||||
return 0, nil, fmt.Errorf("pipe2: %s", e)
|
||||
}
|
||||
|
||||
return p[0], os.NewFile(uintptr(p[1]), "|1"), nil
|
||||
}
|
174
v3/pkg/assetserver/webview/responsewriter_linux_purego.go
Normal file
174
v3/pkg/assetserver/webview/responsewriter_linux_purego.go
Normal file
@ -0,0 +1,174 @@
|
||||
//go:build linux && purego
|
||||
// +build linux,purego
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
const (
|
||||
gtk3 = "libgtk-3.so"
|
||||
gtk4 = "libgtk-4.so"
|
||||
)
|
||||
|
||||
var (
|
||||
gtk uintptr
|
||||
webkit uintptr
|
||||
version int
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
// gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||
// if err == nil {
|
||||
// version = 4
|
||||
// return
|
||||
// }
|
||||
// log.Println("Failed to open GTK4: Falling back to GTK3")
|
||||
gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version = 3
|
||||
|
||||
var webkit4 string = "libwebkit2gtk-4.1.so"
|
||||
webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
req uintptr
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
finished bool
|
||||
|
||||
w io.WriteCloser
|
||||
wErr error
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Header() http.Header {
|
||||
if rw.header == nil {
|
||||
rw.header = http.Header{}
|
||||
}
|
||||
return rw.header
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(buf []byte) (int, error) {
|
||||
if rw.finished {
|
||||
return 0, errResponseFinished
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if rw.wErr != nil {
|
||||
return 0, rw.wErr
|
||||
}
|
||||
return rw.w.Write(buf)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
// TODO? Is this ever called? I don't think so!
|
||||
if rw.wroteHeader || rw.finished {
|
||||
return
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
|
||||
contentLength := int64(-1)
|
||||
if sLen := rw.Header().Get(HeaderContentLength); sLen != "" {
|
||||
if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
|
||||
contentLength = pLen
|
||||
}
|
||||
}
|
||||
// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
|
||||
// read FD is given to the InputStream and will be closed there.
|
||||
// Furthermore we especially don't want to have the FD_CLOEXEC
|
||||
rFD, w, err := pipe()
|
||||
if err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
|
||||
return
|
||||
}
|
||||
rw.w = w
|
||||
|
||||
var newStream func(int, bool) uintptr
|
||||
purego.RegisterLibFunc(&newStream, gtk, "g_unix_input_stream_new")
|
||||
var unRef func(uintptr)
|
||||
purego.RegisterLibFunc(&unRef, gtk, "g_object_unref")
|
||||
stream := newStream(rFD, true)
|
||||
|
||||
/* var reqFinish func(uintptr, uintptr, uintptr, uintptr, int64) int
|
||||
purego.RegisterLibFunc(&reqFinish, webkit, "webkit_uri_scheme_request_finish")
|
||||
|
||||
header := rw.Header()
|
||||
defer unRef(stream)
|
||||
if err := reqFinish(rw.req, code, header, stream, contentLength); err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
|
||||
}
|
||||
*/
|
||||
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return
|
||||
}
|
||||
rw.finished = true
|
||||
if rw.w != nil {
|
||||
rw.w.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) finishWithError(code int, err error) {
|
||||
if rw.w != nil {
|
||||
rw.w.Close()
|
||||
rw.w = &nopCloser{io.Discard}
|
||||
}
|
||||
rw.wErr = err
|
||||
|
||||
var newLiteral func(uint32, string, int, string) uintptr // is this correct?
|
||||
purego.RegisterLibFunc(&newLiteral, gtk, "g_error_new_literal")
|
||||
var newQuark func(string) uintptr
|
||||
purego.RegisterLibFunc(&newQuark, gtk, "g_quark_from_string")
|
||||
var freeError func(uintptr)
|
||||
purego.RegisterLibFunc(&freeError, gtk, "g_error_free")
|
||||
var finishError func(uintptr, uintptr)
|
||||
purego.RegisterLibFunc(&finishError, webkit, "webkit_uri_scheme_request_finish_error")
|
||||
|
||||
msg := string(err.Error())
|
||||
//gquark := newQuark(msg)
|
||||
gerr := newLiteral(1, msg, code, msg)
|
||||
finishError(rw.req, gerr)
|
||||
freeError(gerr)
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
func pipe() (r int, w *os.File, err error) {
|
||||
var p [2]int
|
||||
e := syscall.Pipe2(p[0:], 0)
|
||||
if e != nil {
|
||||
return 0, nil, fmt.Errorf("pipe2: %s", e)
|
||||
}
|
||||
|
||||
return p[0], os.NewFile(uintptr(p[1]), "|1"), nil
|
||||
}
|
69
v3/pkg/assetserver/webview/webkit2_36+.go
Normal file
69
v3/pkg/assetserver/webview/webkit2_36+.go
Normal file
@ -0,0 +1,69 @@
|
||||
//go:build linux && (webkit2_36 || webkit2_40)
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 libsoup-2.4
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
#include "libsoup/soup.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string {
|
||||
method := C.GoString(C.webkit_uri_scheme_request_get_http_method(req))
|
||||
return strings.ToUpper(method)
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_headers(req *C.WebKitURISchemeRequest) http.Header {
|
||||
hdrs := C.webkit_uri_scheme_request_get_http_headers(req)
|
||||
|
||||
var iter C.SoupMessageHeadersIter
|
||||
C.soup_message_headers_iter_init(&iter, hdrs)
|
||||
|
||||
var name *C.char
|
||||
var value *C.char
|
||||
|
||||
h := http.Header{}
|
||||
for C.soup_message_headers_iter_next(&iter, &name, &value) != 0 {
|
||||
h.Add(C.GoString(name), C.GoString(value))
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error {
|
||||
resp := C.webkit_uri_scheme_response_new(stream, C.gint64(streamLength))
|
||||
defer C.g_object_unref(C.gpointer(resp))
|
||||
|
||||
cReason := C.CString(http.StatusText(code))
|
||||
C.webkit_uri_scheme_response_set_status(resp, C.guint(code), cReason)
|
||||
C.free(unsafe.Pointer(cReason))
|
||||
|
||||
cMimeType := C.CString(header.Get(HeaderContentType))
|
||||
C.webkit_uri_scheme_response_set_content_type(resp, cMimeType)
|
||||
C.free(unsafe.Pointer(cMimeType))
|
||||
|
||||
hdrs := C.soup_message_headers_new(C.SOUP_MESSAGE_HEADERS_RESPONSE)
|
||||
for name, values := range header {
|
||||
cName := C.CString(name)
|
||||
for _, value := range values {
|
||||
cValue := C.CString(value)
|
||||
C.soup_message_headers_append(hdrs, cName, cValue)
|
||||
C.free(unsafe.Pointer(cValue))
|
||||
}
|
||||
C.free(unsafe.Pointer(cName))
|
||||
}
|
||||
|
||||
C.webkit_uri_scheme_response_set_http_headers(resp, hdrs)
|
||||
|
||||
C.webkit_uri_scheme_request_finish_with_response(req, resp)
|
||||
return nil
|
||||
}
|
94
v3/pkg/assetserver/webview/webkit2_36+_purego.go
Normal file
94
v3/pkg/assetserver/webview/webkit2_36+_purego.go
Normal file
@ -0,0 +1,94 @@
|
||||
//go:build linux && (webkit2_36 || webkit2_40) && purego
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
func webkit_uri_scheme_request_get_http_method(req uintptr) string {
|
||||
var getMethod func(uintptr) string
|
||||
purego.RegisterLibFunc(&getMethod, gtk, "webkit_uri_scheme_request_get_http_method")
|
||||
return strings.ToUpper(getMethod(req))
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_headers(req uintptr) http.Header {
|
||||
var getHeaders func(uintptr) uintptr
|
||||
purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_http_headers")
|
||||
|
||||
hdrs := getHeaders(req)
|
||||
|
||||
var headersIterInit func(uintptr, uintptr) uintptr
|
||||
purego.RegisterLibFunc(&headersIterInit, gtk, "soup_message_headers_iter_init")
|
||||
|
||||
// TODO: How do we get a struct?
|
||||
/*
|
||||
typedef struct {
|
||||
SoupMessageHeaders *hdrs;
|
||||
int index_common;
|
||||
int index_uncommon;
|
||||
} SoupMessageHeadersIterReal;
|
||||
*/
|
||||
iter := make([]byte, 12)
|
||||
headersIterInit(&iter, hdrs)
|
||||
|
||||
var iterNext func(uintptr, *string, *string) int
|
||||
purego.RegisterLibFunc(&iterNext, gtk, "soup_message_headers_iter_next")
|
||||
|
||||
var name string
|
||||
var value string
|
||||
h := http.Header{}
|
||||
|
||||
for iterNext(&iter, &name, &value) != 0 {
|
||||
h.Add(name, value)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error {
|
||||
|
||||
var newResponse func(uintptr, int64) string
|
||||
purego.RegisterLibFunc(&newResponse, webkit, "webkit_uri_scheme_response_new")
|
||||
var unRef func(uintptr)
|
||||
purego.RegisterLibFunc(&unRef, gtk, "g_object_unref")
|
||||
|
||||
resp := newResponse(stream, streamLength)
|
||||
defer unRef(resp)
|
||||
|
||||
var setStatus func(uintptr, int, string)
|
||||
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_status")
|
||||
|
||||
setStatus(resp, code, cReason)
|
||||
|
||||
var setContentType func(uintptr, string)
|
||||
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_content_type")
|
||||
|
||||
setContentType(resp, header.Get(HeaderContentType))
|
||||
|
||||
soup := gtk
|
||||
var soupHeadersNew func(int) uintptr
|
||||
purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_new")
|
||||
var soupHeadersAppend func(uintptr, string, string)
|
||||
purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_append")
|
||||
|
||||
hdrs := soupHeadersNew(SOUP_MESSAGE_HEADERS_RESPONSE)
|
||||
for name, values := range header {
|
||||
for _, value := range values {
|
||||
soupHeadersAppend(hdrs, name, value)
|
||||
}
|
||||
}
|
||||
|
||||
var setHttpHeaders func(uintptr, uintptr)
|
||||
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_http_headers")
|
||||
|
||||
setHttpHeaders(resp, hdrs)
|
||||
var finishWithResponse func(uintptr, uintptr)
|
||||
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_request_finish_with_response")
|
||||
finishWithResponse(req, resp)
|
||||
|
||||
return nil
|
||||
}
|
21
v3/pkg/assetserver/webview/webkit2_36.go
Normal file
21
v3/pkg/assetserver/webview/webkit2_36.go
Normal file
@ -0,0 +1,21 @@
|
||||
//go:build linux && webkit2_36
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: webkit2gtk-4.0
|
||||
|
||||
#include "webkit2/webkit2.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const Webkit2MinMinorVersion = 36
|
||||
|
||||
func webkit_uri_scheme_request_get_http_body(_ *C.WebKitURISchemeRequest) io.ReadCloser {
|
||||
return http.NoBody
|
||||
}
|
83
v3/pkg/assetserver/webview/webkit2_40+.go
Normal file
83
v3/pkg/assetserver/webview/webkit2_40+.go
Normal file
@ -0,0 +1,83 @@
|
||||
//go:build linux && webkit2_40
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
#include "gio/gunixinputstream.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser {
|
||||
stream := C.webkit_uri_scheme_request_get_http_body(req)
|
||||
if stream == nil {
|
||||
return http.NoBody
|
||||
}
|
||||
return &webkitRequestBody{stream: stream}
|
||||
}
|
||||
|
||||
type webkitRequestBody struct {
|
||||
stream *C.GInputStream
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Read implements io.Reader
|
||||
func (r *webkitRequestBody) Read(p []byte) (int, error) {
|
||||
if r.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
var content unsafe.Pointer
|
||||
var contentLen int
|
||||
if p != nil {
|
||||
content = unsafe.Pointer(&p[0])
|
||||
contentLen = len(p)
|
||||
}
|
||||
|
||||
var n C.gsize
|
||||
var gErr *C.GError
|
||||
res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr)
|
||||
if res == 0 {
|
||||
return 0, formatGError("stream read failed", gErr)
|
||||
} else if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func (r *webkitRequestBody) Close() error {
|
||||
if r.closed {
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
|
||||
// https://docs.gtk.org/gio/method.InputStream.close.html
|
||||
// Streams will be automatically closed when the last reference is dropped, but you might want to call this function
|
||||
// to make sure resources are released as early as possible.
|
||||
var err error
|
||||
var gErr *C.GError
|
||||
if C.g_input_stream_close(r.stream, nil, &gErr) == 0 {
|
||||
err = formatGError("stream close failed", gErr)
|
||||
}
|
||||
C.g_object_unref(C.gpointer(r.stream))
|
||||
r.stream = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func formatGError(msg string, gErr *C.GError, args ...any) error {
|
||||
if gErr != nil && gErr.message != nil {
|
||||
msg += ": " + C.GoString(gErr.message)
|
||||
C.g_error_free(gErr)
|
||||
}
|
||||
return fmt.Errorf(msg, args...)
|
||||
}
|
74
v3/pkg/assetserver/webview/webkit2_40+_purego.go
Normal file
74
v3/pkg/assetserver/webview/webkit2_40+_purego.go
Normal file
@ -0,0 +1,74 @@
|
||||
//go:build linux && webkit2_40 && purego
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser {
|
||||
stream := C.webkit_uri_scheme_request_get_http_body(req)
|
||||
if stream == nil {
|
||||
return http.NoBody
|
||||
}
|
||||
return &webkitRequestBody{stream: stream}
|
||||
}
|
||||
|
||||
type webkitRequestBody struct {
|
||||
stream *C.GInputStream
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Read implements io.Reader
|
||||
func (r *webkitRequestBody) Read(p []byte) (int, error) {
|
||||
if r.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
var content unsafe.Pointer
|
||||
var contentLen int
|
||||
if p != nil {
|
||||
content = unsafe.Pointer(&p[0])
|
||||
contentLen = len(p)
|
||||
}
|
||||
|
||||
var n C.gsize
|
||||
var gErr *C.GError
|
||||
res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr)
|
||||
if res == 0 {
|
||||
return 0, formatGError("stream read failed", gErr)
|
||||
} else if n == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return int(n), nil
|
||||
}
|
||||
|
||||
func (r *webkitRequestBody) Close() error {
|
||||
if r.closed {
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
|
||||
// https://docs.gtk.org/gio/method.InputStream.close.html
|
||||
// Streams will be automatically closed when the last reference is dropped, but you might want to call this function
|
||||
// to make sure resources are released as early as possible.
|
||||
var err error
|
||||
var gErr *C.GError
|
||||
if C.g_input_stream_close(r.stream, nil, &gErr) == 0 {
|
||||
err = formatGError("stream close failed", gErr)
|
||||
}
|
||||
C.g_object_unref(C.gpointer(r.stream))
|
||||
r.stream = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func formatGError(msg string, gErr *C.GError, args ...any) error {
|
||||
if gErr != nil && gErr.message != nil {
|
||||
msg += ": " + C.GoString(gErr.message)
|
||||
C.g_error_free(gErr)
|
||||
}
|
||||
return fmt.Errorf(msg, args...)
|
||||
}
|
5
v3/pkg/assetserver/webview/webkit2_40.go
Normal file
5
v3/pkg/assetserver/webview/webkit2_40.go
Normal file
@ -0,0 +1,5 @@
|
||||
//go:build linux && webkit2_40
|
||||
|
||||
package webview
|
||||
|
||||
const Webkit2MinMinorVersion = 40
|
48
v3/pkg/assetserver/webview/webkit2_legacy.go
Normal file
48
v3/pkg/assetserver/webview/webkit2_legacy.go
Normal file
@ -0,0 +1,48 @@
|
||||
//go:build linux && !(webkit2_36 || webkit2_40)
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const Webkit2MinMinorVersion = 0
|
||||
|
||||
func webkit_uri_scheme_request_get_http_method(_ *C.WebKitURISchemeRequest) string {
|
||||
return http.MethodGet
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_headers(_ *C.WebKitURISchemeRequest) http.Header {
|
||||
// Fake some basic default headers that are needed if e.g. request are being proxied to the an external sever, like
|
||||
// we do in the devserver.
|
||||
h := http.Header{}
|
||||
h.Add("Accept", "*/*")
|
||||
h.Add("User-Agent", "wails.io/605.1.15")
|
||||
return h
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_body(_ *C.WebKitURISchemeRequest) io.ReadCloser {
|
||||
return http.NoBody
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error {
|
||||
if code != http.StatusOK {
|
||||
return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code))
|
||||
}
|
||||
|
||||
cMimeType := C.CString(header.Get(HeaderContentType))
|
||||
C.webkit_uri_scheme_request_finish(req, stream, C.gint64(streamLength), cMimeType)
|
||||
C.free(unsafe.Pointer(cMimeType))
|
||||
return nil
|
||||
}
|
36
v3/pkg/assetserver/webview/webkit2_legacy_purego.go
Normal file
36
v3/pkg/assetserver/webview/webkit2_legacy_purego.go
Normal file
@ -0,0 +1,36 @@
|
||||
//go:build linux && !(webkit2_36 || webkit2_40) && purego
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
const Webkit2MinMinorVersion = 0
|
||||
|
||||
func webkit_uri_scheme_request_get_http_method(_ uintptr) string {
|
||||
return http.MethodGet
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_headers(_ uintptr) http.Header {
|
||||
return http.Header{}
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_body(_ uintptr) io.ReadCloser {
|
||||
return http.NoBody
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error {
|
||||
if code != http.StatusOK {
|
||||
return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code))
|
||||
}
|
||||
|
||||
var requestFinish func(uintptr, uintptr, int64, string)
|
||||
purego.RegisterLibFunc(&requestFinish, webkit, "webkit_uri_scheme_request_finish")
|
||||
requestFinish(req, stream, streamLength, header.Get(HeaderContentType))
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user