How do you use Go 1.16 embed features in subfolders/packages? - go

Go 1.16 is out and I want to use the new embed features. I can get it to work if everything is in the main package. But it's not clear how to handle accessing resources from subfolders/packages. Trying to do it with embed.FS support.
e.g. I have a main.go and also an HTTP handler in a handlers package/folder
If I put the handler in the main, it works. If I put it in the handlers package, it can't find the templates. I get:
handlers/indexHandler.go:11:12: pattern templates: no matching files found exit status 1
Similarly, I can get it to serve an image from the static folder if I serve it from /. But I can't serve both a handler from / and the static/images from /. If I put images on /static/ it can't find the images.
I think it has to do with relative paths. But I can't find the right combination through trial and error... Could it be too early to rely on these features?
Previously I was using go-rice and I did not have these problems. But I would like to use the std library as much as possible.
main.go:
package main
import (...)
//go:embed static
var static embed.FS
func main() {
fsRoot, _ := fs.Sub(static, "static")
fsStatic := http.FileServer(http.FS(fsRoot))
http.Handle("/", fsStatic)
http.HandleFunc("/index", Index)
http.ListenAndServe(":8080", nil)
}
handlers/indexHandler.go:
package handlers
import (...)
//go:embed templates
var templates embed.FS
// Index handler
func Index(w http.ResponseWriter, r *http.Request) {
tmpl := template.New("")
var err error
if tmpl, err = tmpl.ParseFS(templates, "simple.gohtml"); err != nil {
fmt.Println(err)
}
if err = tmpl.ExecuteTemplate(w, "simple", nil); err != nil {
log.Print(err)
}
}
Structure is as follows...
.
├── go.mod
├── go.sum
├── handlers
│ └── indexHandler.go
├── main.go
├── static
│ ├── css
│ │ └── layout.css
│ └── images
│ └── logo.png
└── templates
└── simple.gohtml

I finally figured it out...
You can keep the templates folder in the main folder and embed them from there. Then you need to inject the FS variable into the other handler package. It's always easy after you figure it out.
e.g.
package main
//go:embed templates/*
var templateFs embed.FS
func main() {
handlers.TemplateFs = templateFs
...
package handlers
var TemplateFs embed.FS
func handlerIndex() {
...
tmpl, err = tmpl.ParseFS(TemplateFs, "templates/layout.gohtml",...
...

Currently in Go 1.17 , embed is not supporting subfolders/packages, see https://github.com/golang/go/issues/41191#issuecomment-686616556

Related

golang app with embed static files using reverse proxy

The code first.
package main
import (
"log"
"io/fs"
"embed"
"net/http"
"github.com/gorilla/mux"
)
//go:embed static/*
var static embed.FS
func main() {
router := mux.NewRouter()
fSys, err := fs.Sub(static, "static")
if err != nil {
panic(err)
}
staticServer := http.FileServer(http.FS(fSys))
router.Handle("/", staticServer)
if err := http.ListenAndServe(":3001", router); err != nil {
log.Fatalf("server could not run")
}
}
So I have this application and two others app2, app3 on ports 3002 and 3003 accordingly.
I need to proxy those apps using apache2 server.
Urls should be http:///app1 http:///app2 http:///app3
If I write:
ProxyPass /app1 http://localhost:3001/
ProxyPassReverse /app1 http://localhost:3001/
so index.html file loads fine but css style not loaded.
html and css files are on the same "virtual" disk. How can I fix css loading?
├── go.mod
├── go.sum
├── main.go
└── static
├── index.html
└── style.css
index.html looks like
<link rel="stylesheet" href="./style.css">
<h1>Hello</h1>
Error: mesage in browser console:
The resource from “http://localhost:3001/style.css” was blocked due to MIME type (“text/plain”) mismatch (X-Content-Type-Options: nosniff)

Golang template can't access file in embedFS

my golang code arch is as bellow:
├── embeded.go
├── go.mod
├── json
│   └── file
└── main.go
and this is my embede.go code:
package main
import "embed"
//go:embed json/*
var templatesFS embed.FS
func TemplatesFS() embed.FS {
return templatesFS
}
now in my main.go I can't access file in json directory:
package main
import (
"fmt"
"log"
"os"
"text/template"
)
func main() {
tmpl := template.Must(
template.New("json/file").
ParseFS(TemplatesFS(), "json/file"))
if err := tmpl.Execute(os.Stdout, "config"); err != nil {
log.Fatal(err)
}
}
when I run above code I got error template: json/file: "json/file" is an incomplete or empty template
but I can access the file like this:
file, err := TemplatesFS().ReadFile("json/file")
so why I can't access it in templte.execute ?
How Can I Solve It ?
The template parser successfully read a template from the embedded file system.
The Execute method reports that the template tmpl is incomplete. The variable tmpl is set to the template created by the call to New. The template is incomplete because the application did not parse a template with the template's name, json/file.
ParseFS names templates using the file's base name. Fix by using using the file's base name in the call to New.
tmpl := template.Must(
template.New("file").ParseFS(TemplatesFS(), "json/file"))

plugin and package scoped global variables

I have a Go package to manage configuration. The package has a non-exported variable initialized in an init function that holds the configuration data. The user interacts with the configuration through exported functions that internally access the global variable. Something like this
pakage config
var gConfig ...
func init() {
gConfig = ...
}
func Value(name string) (string, error) {
return gConfig.value(name)
}
I’m considering the use of plugins and explore the impact on my config package.
If the plugin imports the config package and calls some of its exported functions, what gConfig variable will be used? Will the plugin have its own internal instance of config with its own gConfig variable initialized when the plugin is loaded, or will the plugin be linked dynamically at load time to use the main program gConfig variable initialized at program startup?
as per documentation
Package plugin implements loading and symbol resolution of Go plugins.
When a plugin is first opened, the init functions of all packages not
already part of the program are called. The main function is not run.
A plugin is only initialized once, and cannot be closed.
Also, you cannot import the same plugin twice.
Will the plugin have its own internal instance of config with its own gConfig variable initialized when the plugin is loaded
the plugin will have its own variable within its scope.
If the plugin imports the config package and calls some of its exported functions, what gConfig variable will be used ?
the variable defined within the package, as you demonstrated.
To check that out, write a small demonstration. Go is very straightforward and efficient, doing it takes very little time, see.
$ tree .
.
├── main.go
├── plug
│   └── plugin.go
└── plugin.so
1 directory, 3 files
// $ cat plug/plugin.go
package main
var pkgGlobal = map[string]string{}
func Set(k, v string) {
pkgGlobal[k] = v
}
func Get(k string) string {
return pkgGlobal[k]
}
// $ cat main.go
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("plugin.so")
if err != nil {
panic(err)
}
var get func(string) string
{
x, err := p.Lookup("Get")
if err != nil {
panic(err)
}
get = x.(func(string) string)
}
var set func(string, string)
{
x, err := p.Lookup("Set")
if err != nil {
panic(err)
}
set = x.(func(string, string))
}
set("tomate", "rouge")
fmt.Println(get("tomate"))
fmt.Println(get("notomate"))
}
build and run
$ go build -buildmode=plugin -o plugin.so plug/plugin.go
$ go run main.go
rouge

Importing local package in another folder

I keep getting the following error and I have been trying out some of the other suggestions in other Stack Overflow threads.
./main.go:15:13: undefined: checkEnv
Folder Structure:
├── README.md
├── main.go
└── validate
└── args.go
$GOPATH
/Users/myusername/go
main.go
package main
import (
"fmt"
"log"
"os"
"projectName/validate"
"gopkg.in/urfave/cli.v1"
)
func main() {
app := cli.NewApp()
app.Name = "thirsty"
app.Action = func(c *cli.Context) error {
result := checkEnv(c.Args().Get(0))
fmt.Println(result)
return nil
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
validate/args.go
package validate
import "strings"
func checkEnv(environment string) bool {
env := strings.ToLower(environment)
return env != "qa" && env != "dev"
}
My project is in the src directory of my $GOPATH. If this is not the proper way to do code splitting, is there a generally used convention to follow?
Any feedback appreciated.
There are two problems here.
The checkEnv method is not exported [in validate/args.go]. ie it is usable only inside the same package and not other packages To export just capitalize the first letter of the method CheckEnv. Now CheckEnv can be used from other packages
While calling a method from other package, the syntax should be packagename.ExportedMethod(params...). so in your code [in main.go] it should be result := validate.CheckEnv(c.Args().Get(0))

How to include go templates when unit testing

I'm trying to test an implementation of a simple web server in Go using the Gin.
The service has a single endpoint rendering HTML.
server.go
// Serve triggers the server initialization
func Serve(addr string) {
if err := serverEngine().Run(addr); err != nil {
log.Fatalf("could not serve on %s: %s", addr, err)
}
}
func serverEngine() *gin.Engine {
eng := gin.Default()
// Register resource handlers
eng.LoadHTMLGlob("tmpl/*")
eng.GET("/", indexHandler)
return eng
}
func indexHandler(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "foo",
})
}
server_test.go
func TestServe(t *testing.T) {
timeOut := time.Duration(3) * time.Second
server := httptest.NewServer(serverEngine())
defer server.Close()
// fixes weird double ':' problem
port := server.URL[len(server.URL)-5:]
_, err := net.DialTimeout("tcp", "localhost:"+port, timeOut)
if err != nil {
t.Errorf("failed to dial server: %s", err)
}
}
When I run the code everything works fine. But when running the unit tests it panics with the following message:
--- FAIL: TestServe (0.00s)
panic: html/template: pattern matches no files: `tmpl/*` [recovered]
panic: html/template: pattern matches no files: `tmpl/*`
Project structure:
.
├── main.go
└── server
├── server.go
├── server_test.go
└── tmpl
└── index.tmpl
How can I ensure that the go test know about the template location at runtime so I can perform this test?
EDIT:
After comment interaction, updating answer match directory structure.
gin-test/
├── main.go
└── server
├── server.go
├── server_test.go
└── tmpl
└── index.tmpl
And your server.go file on function serverEngine() has-
eng.LoadHTMLGlob("tmpl/*")
First you're in root directory: i.e. gin-test, run-
# Output:
jeeva#mb-pro:~/go-home/src/gin-test$ go test ./...
? gin-test [no test files]
ok gin-test/server 0.046s
Second you're in gin-test/server directory/package:
jeeva#mb-pro:~/go-home/src/gin-test/server$ go test
#Output:
It would produce the same output as below answer.
My best guess is 'problem could be your directory structure and files' and bit of test case code improvements.
I have just tried your code at my local with following directory structure:
$GOPATH/src/gin-test/
├── server.go
├── server_test.go
└── tmpl
└── index.tmpl
Then, I have updated your TestServe method to following:
Note: your test case works too, I just updated to demo the GET request and response.
func TestServe(t *testing.T) {
server := httptest.NewServer(serverEngine())
defer server.Close()
fmt.Println("Server URL:", server.URL)
resp, err := http.Get(server.URL)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println("Error:", err)
fmt.Println("Response:", string(body))
}
Unit test output:
jeeva#mb-pro:~/go-home/src/gin-test$ go test
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] Loaded HTML Templates (1):
- index.tmpl
[GIN-debug] GET / --> gin-test.indexHandler (3 handlers)
Server URL: http://127.0.0.1:56989
[GIN] 2017/07/09 - 16:31:16 | 200 | 432.107µs | 127.0.0.1 | GET /
Error: <nil>
Response: <b>I'm here</b>
PASS
ok gin-test 0.104s
jeeva#mb-pro:~/go-home/src/gin-test$
Happy coding!

Resources