I am trying to include CSS and JS files in my html templates using Go.
Here is my code
main.go
package main
import (
"fmt"
"net/http"
)
func main() {
var mux = http.NewServeMux()
registerRoutes(mux)
httpServer := http.Server{
Addr: ":3000",
Handler: mux,
}
err := httpServer.ListenAndServe()
if err != nil {
fmt.Print(err)
}
}
routes.go
package main
import "net/http"
func registerRoutes(mux *http.ServeMux) {
mux.HandleFunc("/", index)
mux.HandleFunc("/faq", faq)
}
handlers.go
package main
import (
"fmt"
"html/template"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("templates/index.html")
if err != nil {
fmt.Print(err)
}
err = tmpl.Execute(w, nil)
if err != nil {
fmt.Print(err)
}
}
func faq(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("templates/faq.html")
if err != nil {
fmt.Print(err)
}
err = tmpl.Execute(w, nil)
if err != nil {
fmt.Print(err)
}
}
index.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Index</title>
<link href="../static/stylesheets/main.css" type="text/css" rel="stylesheet">
<script type="text/javascript" src="../static/scripts/index.js"></script>
</head>
<body>
<h1>Test</h1>
</body>
</html>
Rendering the HTML works, but it does not include the CSS or JS files. How can I get it to recognise them?
Thanks.
EDIT: As suggested by #Burak Serdar, I have implemented the following code:
Added this to handlers.go
func staticHandler(w http.ResponseWriter, r *http.Request) {
// load the file using r.URL.Path, like /static/scripts/index.js"
path := r.URL.Path
data, err := ioutil.ReadFile(path)
if err != nil {
fmt.Print(err)
}
if strings.HasSuffix(path, "js") {
w.Header().Set("Content-Type","text/javascript")
} else {
w.Header().Set("Content-Type","text/css")
}
_, err = w.Write(data)
if err != nil {
fmt.Print(err)
}
}
Added this to routes.go
mux.HandleFunc("/static", staticHandler)
However, it still does not work.
Perhaps I should note that static/ and templates/ are in the same folder, and these share the folder with main.go etc.
EDIT2: It seems like my method may not be the best, and so I am trying to use the inbuilt FileServer. However, I can't quite work out how to do it.
I added this line
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("C:/Users/x/Documents/Coding/Go/hello_world/"))))
to registerRoutes but it's not working.
EDIT3: Perhaps I should make it clear what I am trying to achieve. I am attempting to reach Flask-like functionality in Go. This means redirecting to certain templates based on the path visited. What I mean by a template is a .html file that I can pass variables to.
EDIT4: I think I have achieved what I wanted. I added
fs := http.FileServer(http.Dir("static"))
mux.Handle("/static/", http.StripPrefix("/static", fs))
to my main() function in main.go. Then removed mux.HandleFunc("/static/", staticHandler) and the staticHandler function.
EDIT5: Assuming that is a good method, my last concern is how to handle caching. It is clear that I am parsing the files every time the page is rendered with tmpl, err := template.ParseFiles("templates/index.html") etc. As such, I thought perhaps I could add a function to load these files and return the templates, then call this function in main and pass the variables to the handlers. Is this a good idea? How would you go about doing it exactly? Does this mean my files only get updated when I restart my web server?
For example
func initTemplates() (*template.Template, *template.Template) {
indexTemplate := template.Must(template.ParseFiles("templates/index.html"))
faqTemplate := template.Must(template.ParseFiles("templates/faq.html"))
return indexTemplate, faqTemplate
}
func main() {
var mux = http.NewServeMux()
fs := http.FileServer(http.Dir("static"))
mux.Handle("/static/", http.StripPrefix("/static", fs))
indexTemplate, faqTemplate := initTemplates()
...
}
My problem with this is it seems strange to have a variable here like this for each page on my website. What if I want to have 100 pages? Additionally, how do I even pass these variables to my handling functions defined above?
EDIT6:
How about this?
main.go
var templates map[string]*template.Template
func init() {
if templates == nil {
templates = make(map[string]*template.Template)
}
templates["index.html"] = template.Must(template.ParseFiles("templates/index.html"))
templates["faq.html"] = template.Must(template.ParseFiles("templates/faq.html"))
}
func main() {
var mux = http.NewServeMux()
fs := http.FileServer(http.Dir("static"))
mux.Handle("/static/", http.StripPrefix("/static", fs))
registerRoutes(mux)
httpServer := http.Server{
Addr: ":3000",
Handler: mux,
}
err := httpServer.ListenAndServe()
if err != nil {
fmt.Print(err)
}
}
Then in handlers.go I use tmpl := templates["index.html"]
EDIT7: Not sure if I should make a new question at this point but I'll just keep going.
I ran into a problem when trying to service the resource /purchase/license. Now the server is looking in /purchase/static/stylesheets/main.css on that page. How can I resolve this?
EDIT8: I resolved my previous edit by adding
mux.Handle("/purchase/static/", http.StripPrefix("/purchase/static", fs))
to main(). Is there a better, more scaleable way of resolving this? What if I have hundreds of /x/y, do I really need to add one of these for each x? Could I use regex or something to add a wildcard like this?
mux.Handle("*/static/", http.StripPrefix("*/static", fs))
If so, how would I do that exactly?
You have to "serve" the JS and CSS files as well.
The template is rendered and sent as an HTML page. Once the HTML page is loaded, the browser tries to load the CSS and JS files using the links in the page. Since your server does not handle those routes, the browser cannot load them.
I suggest instead of using ../static path, use /static in the HTML, and mount that route to your router with a handler that loads the CSS and JS files and returns them. You also have to set the Content-Type of the response so the browser can use those files correctly.
// Register the handler
mux.HandleFunc("/static", staticHandler)
func staticHandler(w http.ResponseWriter, r *http.Request) {
// load the file using r.URL.Path, like /static/scripts/index.js"
data,err:=ioutil.ReadFile(...)
// Figure out file type:
if strings.EndsWith("js") {
w.Header.Set("Content-Type","text/javascript")
} else {
w.Header.Set("Content-Type","text/css")
}
w.Write(data)
}
Thanks to #Burak Serdar for the guidance, I fixed some of his code and added a few things to get a solution.
Added this to handlers.go
func staticHandler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if strings.HasSuffix(path, "js") {
w.Header().Set("Content-Type","text/javascript")
} else {
w.Header().Set("Content-Type","text/css")
}
data, err := ioutil.ReadFile(path[1:])
if err != nil {
fmt.Print(err)
}
_, err = w.Write(data)
if err != nil {
fmt.Print(err)
}
}
Added this to routes.go
mux.HandleFunc("/static/", staticHandler)
Related
Trying to move my golang html templates from files to using embed
Works fine:
func loadTemplates() multitemplate.Render {
r := multitemplate.New()
layouts, err := filepath.Glob("templates/layouts/*.tmpl")
if err != nil {
panic(err.Error())
}
includes, err := filepath.Glob("templates/includes/*.tmpl")
if err != nil {
panic(err.Error())
}
// Generate our templates map from our layouts/ and includes/ directories
for _, layout := range layouts {
files := append(includes, layout)
r.Add(filepath.Base(layout), template.Must(template.ParseFiles(files...)))
log.Println(filepath.Base(layout) + ": " + files[0])
}
return r
}
Very similar code returns blank page, no errors:
//go:embed templates/*
var f embed.FS
func loadTemplates() multitemplate.Render {
r := multitemplate.New()
// Generate our templates map from our layouts/ and includes/ directories
layouts, err := embed.FS.ReadDir(f, "templates/layouts")
if err != nil {
panic(err.Error())
}
for _, layout := range layouts {
embeddedTemplate, err := template.ParseFS(f, "templates/layouts/"+layout.Name(), "templates/includes/base.tmpl")
if err != nil {
log.Println(err)
}
r.Add(layout.Name(), embeddedTemplate)
log.Println(layout.Name() + " loaded")
}
return r
}
I confirmed in the debugger that all templates contain no errors and their respective content. Other embedded files such as static assets work fine and get served ok. Even other templates loaded from a database work fine. Just those from embed end up blank.
Any hints what's happening here?
Thanks!
Edit: Full example:
main.go
package main
import (
"embed"
"html/template"
"log"
"path/filepath"
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
)
//go:embed templates/*
var f embed.FS
func main() {
router := gin.Default()
router.HTMLRender = loadTemplates()
router.GET("/embed", HomeHandlerEmbed(router))
router.GET("/file", HomeHandlerFile(router))
router.Run(":8080")
}
func loadTemplates() multitemplate.Render {
r := multitemplate.New()
//load same template from embed FS
embeddedTemplate, err := template.ParseFS(f, "templates/layouts/home.tmpl", "templates/includes/base.tmpl")
if err != nil {
log.Println(err)
}
r.Add("homeEmbed.tmpl", embeddedTemplate)
log.Println("homeEmbed.tmpl" + " loaded from embed FS")
// load same template from real file system
layoutsFile, err := filepath.Glob("templates/layouts/*.tmpl")
if err != nil {
panic(err.Error())
}
includes, err := filepath.Glob("templates/includes/*.tmpl")
if err != nil {
panic(err.Error())
}
for _, layout := range layoutsFile {
files := append(includes, layout)
r.Add(filepath.Base(layout), template.Must(template.ParseFiles(files...)))
log.Println(filepath.Base(layout) + ": " + files[0])
}
return r
}
func HomeHandlerEmbed(r *gin.Engine) gin.HandlerFunc {
return gin.HandlerFunc(func(c *gin.Context) {
c.HTML(200, "homeEmbed.tmpl", nil)
})
}
func HomeHandlerFile(r *gin.Engine) gin.HandlerFunc {
return gin.HandlerFunc(func(c *gin.Context) {
c.HTML(200, "home.tmpl", nil)
})
}
templates/includes/base.tmpl
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
{{template "body" .}}
</body>
</html>
templates/layouts/home.tmpl
{{define "head"}}<title>Test</title>{{end}}
{{define "body"}}
Body
{{end}}
/file works fine, /embed comes up blank
In function loadTemplates() just fix this line:
embeddedTemplate, err := template.ParseFS(f, "templates/includes/base.tmpl", "templates/layouts/home.tmpl")
In your example patterns will be presented in this sequence:
first: "templates/layouts/home.tmpl"
second: "templates/includes/base.tmpl"
But if I understood correctly, the sequence of patterns is important for the function template.ParseFS, because base.tmpl will be included in all you templates.
The function template.ParseFS reads the templates in the process and tries to generate them.
I am very beginner of Go language. I an trying to serve static containt with Gorrila Mux router. But css and js is not server in my case.
project
f-mymux.go
d-pages
f-home.html
f-about.html
d-public
d-css
f-style.css
d-js
f-script.js
Note: f- file & d- directory
My GO code as below:
package main
import (
"bufio"
"github.com/gorilla/mux"
"log"
"net/http"
"os"
"strings"
"text/template"
)
func main() {
serverWeb()
}
var staticPages = populateStaticPages()
func serverWeb() {
gorillaRoute := mux.NewRouter().StrictSlash(true)
gorillaRoute.HandleFunc("/", serveContent)
gorillaRoute.HandleFunc("/{page_alias}", serveContent)
gorillaRoute.HandleFunc("/css", serveResource)
port := ":8080"
log.Println("Listening at port :", port)
http.Handle("/", gorillaRoute)
err := http.ListenAndServe(port, nil)
if err == nil {
log.Fatal(err)
}
}
func serveContent(w http.ResponseWriter, r *http.Request) {
pathX := r.URL.Path
log.Println(pathX)
urlParams := mux.Vars(r)
page_alias := urlParams["page_alias"]
if page_alias == "" {
page_alias = "home"
}
staticPage := staticPages.Lookup(page_alias + ".html")
if staticPage == nil {
staticPage = staticPages.Lookup("404.html")
w.WriteHeader(404)
}
staticPage.Execute(w, nil)
}
func populateStaticPages() *template.Template {
result := template.New("template")
templatePaths := new([]string)
basePath := "pages"
templateFolder, _ := os.Open(basePath)
defer templateFolder.Close()
templatePathsRow, _ := templateFolder.Readdir(-1)
for _, pathInfo := range templatePathsRow {
log.Println(pathInfo.Name())
*templatePaths = append(*templatePaths, basePath+"/"+
pathInfo.Name())
}
result.ParseFiles(*templatePaths...)
return result
}
//---------------------------------------------
// Serve resource of types css, js & img files
//---------------------------------------------
func serveResource(w http.ResponseWriter, r *http.Request) {
path := "./public" + r.URL.Path
var contentType string
if strings.HasSuffix(path, ".css") {
contentType = "text/css; charset=utf-8"
} /* else if strings.HasSuffix(path , ".png"){
contentType = "image/png; charset=utf-8"
} else if strings.HasSuffix(path , ".jpg"){
contentType = "image/jpg; charset=utf-8"
} else if strings.HasSuffix(path , ".js"){
contentType = "application/javascript; charset=utf-8"
} else {
contentType = "text/plain; charset=utf-8"
}*/
f, err := os.Open(path)
if err == nil {
defer f.Close()
w.Header().Add("Content-Type", contentType)
br := bufio.NewReader(f)
br.WriteTo(w)
} else {
w.WriteHeader(404)
}
}
when I am invoking code
http://localhost:8080/home
then page come witout css and js.When invoking page http://localhost:8080/css/bootstrap.min.css then 404 staus code come
Please help me what am doing wrong here.
We can do easily on Java and java server. But on Go lang I spend a whole day but
unable to resolve the issue.
Your help is appropriated.
Thanks in advance.
As mkopriva stated, http.FileServer is the way to go. Your public folder can be wherever or whatever you want it to be as long as its referenced properly as the http.FileServer argument.
Adding this would work:
fs := http.FileServer(http.Dir("./public"))
gorillaRoute.PathPrefix("/js/").Handler(fs)
gorillaRoute.PathPrefix("/css/").Handler(fs)
That way a GET request to http://[host]:[port]/css/style.css will return style.css from the relative ./public/css/ directory.
I'm working on a website using Go. The server constraints require that I use CGI. When I test the following code locally using http.ListenAndServe() (commented out below), the various handlers are called correctly depending on the address requested. However, if I use cgi.Serve() instead, the default router is executed for all addresses (i.e., the handler for "/" is always executed). I'd appreciate any clues as to how to fix the issue.
EDIT: Here is the simplest test case I can think of to show the problem:
//=============SIMPLIFIED CODE================//
package main
import (
"fmt"
"net/http"
"net/http/cgi"
)
func defaultHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Default")
}
func otherHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Other")
}
func main() {
http.HandleFunc("/other", otherHandler)
http.HandleFunc("/", defaultHandler)
/*
//Works fine
err := http.ListenAndServe(":8090", nil)
if err != nil {
panic(err)
}
*/
//Always fires defaultHandler no matter the address requested
err := cgi.Serve(nil)
if err != nil {
panic(err)
}
}
//=============CODE FROM ORIGINAL POST===================//
package main
import (
"fmt"
"net/http"
"net/http/cgi"
"net/url"
"os"
"github.com/go-cas/cas"
)
func logoutHandler(w http.ResponseWriter, r *http.Request) {
cas.RedirectToLogout(w, r)
}
func calendarHandler(w http.ResponseWriter, r *http.Request) {
if !cas.IsAuthenticated(r) {
cas.RedirectToLogin(w, r)
}
fmt.Fprintf(w, "Calendar for %s", cas.Username(r))
}
func defaultHandler(w http.ResponseWriter, r *http.Request) {
if !cas.IsAuthenticated(r) {
cas.RedirectToLogin(w, r)
}
fmt.Fprintf(w, "Hi there %s!", cas.Username(r))
}
func main() {
u, _ := url.Parse("https://www.examplecasserver.com")
client := cas.NewClient(&cas.Options{
URL: u,
})
http.Handle("/logout", client.HandleFunc(logoutHandler))
http.Handle("/calendar", client.HandleFunc(calendarHandler))
http.Handle("/", client.HandleFunc(defaultHandler))
/*
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
*/
err := cgi.Serve(nil)
if err != nil {
panic(err)
}
}
The CGI program expects some variables to be set in order to build the request.
Probably there is some issue with the configuration of your web server in which the variables are either not set correctly or not named correctly.
To verify this:
1) Add this before calling cgi.Serve and you'll see how the right handler is called (otherHandler)
os.Setenv("REQUEST_METHOD", "get")
os.Setenv("SERVER_PROTOCOL", "HTTP/1.1")
os.Setenv("SCRIPT_NAME", "/other")
2) Add this at the beginning of the main to check how the variables are being set by the web server:
fmt.Println(os.Environ())
In that output, look for the CGI meta variables defined in the spec:
http://www.ietf.org/rfc/rfc3875
Look for the section "Request Meta-Variables" in that page, you are probably looking for the SCRIPT_NAME or PATH_INFO variables.
EDIT
From the variable values you pasted below, it seems the issue is that the REQUEST_URI contains an additional path component:
REQUEST_URI=/main.cgi/other
So the easiest fix would be for you to map the routes accordingly:
http.HandleFunc("/main.cgi/other", otherHandler)
http.HandleFunc("/", defaultHandler) // or maybe /main.cgi
I feel like this a simple problem, but I'm an utter noob and I cant seem to find an answer.
I'm using the following to present a specific html template based on the URL path
func handleTemplate(w http.ResponseWriter, r *http.Request) {
templates := populateTemplates()
requestedFile := r.URL.Path[1:]
t := templates.Lookup(requestedFile + ".html")
if t != nil {
err := t.Execute(w, nil)
if err != nil {
log.Println(err)
}
} else {
w.WriteHeader(http.StatusNotFound)
}
}
func main() {
http.HandleFunc("/", handleTemplate)
http.Handle("/img/", http.FileServer(http.Dir("static")))
http.Handle("/css/", http.FileServer(http.Dir("static")))
// Dev port binding
bind := fmt.Sprintf("%s:%s", "0.0.0.0", "5000")
fmt.Printf("listening on %s...", bind)
err := http.ListenAndServe(bind, nil)
if err != nil {
panic(err)
}
}
So that works great if I have a template called home.html.. I can browse to localhost/home and it will present me the right template.
However I want the user to browse to localhost/ and be presented with a specific html template. Is that possible without a framework?
Sure:
if r.URL.Path == "/" {
// Render default/index/whatever page
}
I am new to GoLang dev and am trying to make a simple web-app. I have been following this tutorial https://www.youtube.com/watch?v=AiRhWG-2nGU.
However I can not even serve the index.html file.
This is my code
func check(e error) {
if e != nil {
fmt.Println(e)
panic(e)
}
}
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Println("Index functoin")
indexHTML, err := ioutil.ReadFile("index.html")
check(err)
fmt.Println(indexHTML)
w.Write(indexHTML)
}
and this is the error produced
Index functoin
open index.html: no such file or directory
My tree structure is like so
BasicWebServer/
BasicWebServer/main.go
BasicWebServer/index.html
BasicWebServer/static/
BasicWebServer/static/index.html
All I want is to be able to serve the index.html since it is a AngularJS app which is already running smoothly. I tried with static files like so
router := NewRouter()
s := http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))
but it did not work so I am now trying the most basic approach I could think of.
Please help.
Thank you
If you want BasicWebServer/main.go to show BasicWebServer/index.html, not the one inside the static folder, then it seems you didn't properly configure the HTTP server.
Here's your code, with package declaration, imports and a main function working as you expected.
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func check(e error) {
if e != nil {
fmt.Println(e)
panic(e)
}
}
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Println("Index functoin")
indexHTML, err := ioutil.ReadFile("index.html")
check(err)
fmt.Println(indexHTML)
w.Write(indexHTML)
}
func main() {
http.HandleFunc("/", Index)
err := http.ListenAndServe(":8080", nil)
check(err)
}