I want to display a compiled angular project under a subpath (eg http://localhost:3000/app/) with go mux
It works as expected without the subpath "app/"
But when I add the subpath with http.StripPrexix("/app/, ..) Handler only index html is found and rendered but without all css, js files..
Test code
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
mux := mux.NewRouter()
fs := http.FileServer(http.Dir("./static/"))
// Serve static files
mux.PathPrefix("/app/").Handler(http.StripPrefix("/app/", fs))
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>TestProject</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.3ff695c00d717f2d2a11.css"></head>
<body>
<h1>Should work</h1>
<app-root></app-root>
<script src="runtime.359d5ee4682f20e936e9.js" defer></script><script src="polyfills.bf99d438b005d57b2b31.js" defer></script><script src="main.5dd083c1a27b2a7e410a.js" defer></script></body>
</html>
Working Project Download
This project also includes the static files to serve
https://filehorst.de/d/dubJmmsz
Goal
Serve different projects under different paths
Following on this question
What am I doing wrong?
How to serve the whole project under e.g.: localhost:3000/app/
The gorilla mux docs mention how to handle Angular SPA applications.
However since you want to base your SPA root to a sub directory, you also need to do the changes in HTML/JS. The base directory should be a variable that will be same as that of prefix of SPA. This is more of a design problem I believe
Otherwise since your rest of the files other than index are resolved at root you need to fallback to "/" for rest of the files & have both configured.
router.PathPrefix("/app").Handler(spa)
router.PathPrefix("/").Handler(spa)
So try like below. I tried your static folder with following:
package main
import (
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/gorilla/mux"
)
type spaHandler struct {
staticPath string
indexPath string
}
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get the absolute path to prevent directory traversal
path, err := filepath.Abs(r.URL.Path)
if err != nil {
// if we failed to get the absolute path respond with a 400 bad request
// and stop
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// prepend the path with the path to the static directory
path = filepath.Join(h.staticPath, path)
// check whether a file exists at the given path
_, err = os.Stat(path)
if os.IsNotExist(err) {
// file does not exist, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
return
} else if err != nil {
// if we got an error (that wasn't that the file doesn't exist) stating the
// file, return a 500 internal server error and stop
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// otherwise, use http.FileServer to serve the static dir
http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
}
func main() {
router := mux.NewRouter()
spa := spaHandler{staticPath: "static", indexPath: "index.html"}
router.PathPrefix("/app").Handler(spa)
srv := &http.Server{
Handler: router,
Addr: ":3000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
Related
I have a GoLang script that is meant to dynamically construct webpages based on an input query in my browser that looks like this http://localhost:8000/blog/post#, the post# portion is what is used to identify which JSON data file to parse into the HTML template I created; for example, if I put http://localhost:8000/blog/post1 then it creates an index.html file from my post1.json file. Currently, my script when run allows me to load a single page in my browser before it exits with the error in my terminal stdout log:
2022/03/18 00:32:02 error: open jsofun.css.json: no such file or directory
exit status 1
this is my current script:
package main
import (
"encoding/json"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
)
func blogHandler(w http.ResponseWriter, r *http.Request) {
blogstr := r.URL.Path[len("/blog/"):]
blogstr = blogstr + ".json"
// read the file in locally
json_file, err := ioutil.ReadFile(blogstr)
if err != nil {
log.Fatal("error: ", err)
}
// define a data structure
type BlogPost struct {
// In order to see these elements later, these fields must be exported
// this means capitalized naming and the json field identified at the end
Title string `json:"title"`
Timestamp string `json:"timestamp"`
Main string `json:"main"`
ContentInfo string `json:"content_info"`
}
// json data
var obj BlogPost
err = json.Unmarshal(json_file, &obj)
if err != nil {
log.Fatal("error: ", err)
}
tmpl, err := template.ParseFiles("./blogtemplate.html")
HTMLfile, err := os.Create("index.html")
if err != nil {
log.Fatal(err)
}
defer HTMLfile.Close()
tmpl.Execute(HTMLfile, obj)
http.ServeFile(w, r, "./index.html")
}
func main() {
http.HandleFunc("/blog/", blogHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
I have done some basic debugging and identified that the issue is coming from the lines:
json_file, err := ioutil.ReadFile(blogstr)
if err != nil {
log.Fatal("error: ", err)
}
What confuses me is why the ioutil.ReadFile is also trying to read files linked within my HTML? Shouldn't the browser be handling that linkage and not my Handler? For reference this is my HTML where the file jsofun.css is linked; the error referenced in my console output shows me my script is trying to access this file as jsofun.css.json during the call to ioutil.ReadFile:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic JSON Events</title>
<link rel="stylesheet" href="./jsofun.css"></style>
</head>
<body>
<section id="title">
<h1 id="text-title">{{.Title}}</h1>
<time id="timestamp">
{{.Timestamp}}
</time>
</section>
<nav role="navigation" id="site-nav">
<ul id="sitemap">
</ul>
</nav>
<main role="main" id="main">
{{.Main}}
</main>
<footer role="contentinfo" id="footer">
<section id="content-info" role="contentinfo">
{{.ContentInfo}}
</section>
<form id="contact-form" role="form">
<address>
Contact me by <a id="my-email" href="mailto:antonhibl11#gmail.com" class="my-email">e-mail</a>
</address>
</form>
</footer>
<script src="./jsofun.js">
</script>
</body>
</html>
I know that I have appended .json to my query string in the line: blogstr = blogstr + ".json", however, I do not see why this would also affect the links in my HTML. Is there something I am missing or why would it be reading links in my HTML template? All I want the ioutil.ReadFile to do is to read my JSON file which is parsed into my template.
EDIT: I wanted to add that when I load the page initially into the browser it loads successfully with my text content from my JSON but fails to have any styling. When I view the source the links also are correct which confuses me why the stylesheet wouldn't apply because it is in the right directory to be accessible. This is the source when I select view-source in browser:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic JSON Events</title>
<link rel="stylesheet" href="./jsofun.css"></style>
</head>
<body>
<section id="title">
<h1 id="text-title">Second Post Test</h1>
<time id="timestamp">
Friday, March 18th, 12:21AM
</time>
</section>
<nav role="navigation" id="site-nav">
<ul id="sitemap">
</ul>
</nav>
<main role="main" id="main">
This is my second post where I am able to use dynamic URL routing
</main>
<footer role="contentinfo" id="footer">
<section id="content-info" role="contentinfo">
This post was used primarily to test and layout how the rest of my posts will appear.
</section>
<form id="contact-form" role="form">
<address>
Contact me by <a id="my-email" href="mailto:antonhibl11#gmail.com" class="my-email">e-mail</a>
</address>
</form>
</footer>
<script src="./jsofun.js">
</script>
</body>
</html>
For reference this is an example of how my JSON file looks:
{
"title" : "Second Post Test",
"timestamp": "Friday, March 18th, 12:21AM",
"main": "This is my second post where I am able to use dynamic URL routing",
"content_info": "This post was used primarily to test and layout how the rest of my posts will appear."
}
Your Go server is set up to only serve the /blog/ path and it does that by executing the blogHandler. There is nothing else in your Go server that is set up to serve assets like css, js, or image files.
For such things you generally need to register a separate FileServer handler at a separate path. Example:
func main() {
http.HandleFunc("/blog/", blogHandler)
// To serve a directory on disk (/path/to/assets/on/my/computer)
// under an alternate URL path (/assets/), use StripPrefix to
// modify the request URL's path before the FileServer sees it:
http.Handle("/assets/", http.StripPrefix("/assets/",
http.FileServer(http.Dir("/path/to/assets/on/my/computer"))))
log.Fatal(http.ListenAndServe(":8080", nil))
}
The other thing you need to fix are the links to those asset fiels in the HTML, they should be absolute, not relative.
...
<link rel="stylesheet" href="/assets/jsofun.css"></style>
...
<script src="/assets/jsofun.js">
The above will of course work only if the assets are located in the /path/to/assets/on/my/computer directory, e.g.
/path/to/assets/on/my/computer
├── jsofun.css
└── jsofun.js
You blogHandler is unnecessarily creating a new file for every request without removing it, this has the potential to very quickly fill up your disk to its max capacity. To serve a template you do not need to create a new file, you can instead execute the template directly into the http.ResposeWriter. It is also advisable to parse the template only once, especially in production code, to avoid unnecessary waste of resources:
type BlogPost struct {
Title string `json:"title"`
Timestamp string `json:"timestamp"`
Main string `json:"main"`
ContentInfo string `json:"content_info"`
}
var blogTemplate = template.Must(template.ParseFiles("./blogtemplate.html"))
func blogHandler(w http.ResponseWriter, r *http.Request) {
blogstr := r.URL.Path[len("/blog/"):] + ".json"
f, err := os.Open(blogstr)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
defer f.Close()
var post BlogPost
if err := json.NewDecoder(f).Decode(&post); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := blogTemplate.Execute(w, post); err != nil {
log.Println(err)
}
}
Lets work through what happens when you request http://localhost:8000/blog/post#.
The browser requests the page; your code successfully builds and returns some html - this will include:
<link rel="stylesheet" href="./jsofun.css"></style>
The browser receives and processes the HTML; as part of that process it requests the css above. Now the original request was in the folder /blog/post# so ./jsofun.css becomes http://localhost:8000/blog/jsofun.css.
When your go application receives this request blogHandler will be called (due to the request path); it strips off the /blog/ then adds .json to get the filename jsofun.css.json. You then try to open this file and get the error because it does not exist.
There are a few ways you can fix this; changing the template to use <link rel="stylesheet" href="/jsofun.css"></style> might be a start (but I don't know where jsofun.css is stored and you don't show any code that would serve that file). I think it's also worth noting that you do not have to create the file index.html on disk (unless there is some other reason for doing this).
(See mkopriva's answer for other issues and further steps - was half way through entering this when that answer was posted and felt the walk through might still be of use).
I want to serve a JSON file with gin server. And set some customize values in the HTML file. Use JavaScript in it to call the JSON file.
My application structure:
.
├── main.go
└── templates
├── index.html
└── web.json
I put these basic source into main.go file:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
var router *gin.Engine
func main() {
router = gin.Default()
router.LoadHTMLGlob("templates/*")
router.GET("/web", func(c *gin.Context) {
c.HTML(
http.StatusOK,
"index.html",
gin.H{
"title": "Web",
"url": "./web.json",
},
)
})
router.Run()
}
Some code in templates/index.html file:
<!doctype html>
<html>
<head>
<title>{{ .title }}</title>
// ...
</head>
<body>
<div id="swagger-ui"></div>
// ...
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "{{ .url }}",
dom_id: '#swagger-ui',
// ...
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
When running the application, I got a fetch error:
Not Found ./web.json
So how should I serve the web.json file to be accessed in the Gin internal server?
Quoting the original gin docs: https://github.com/gin-gonic/gin#serving-static-files
func main() {
router := gin.Default()
router.Static("/assets", "./assets")
router.StaticFS("/more_static", http.Dir("my_file_system"))
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
So basically you should define a route specific to your JSON file next to other routes you've defined. And then use that.
I am currently working on a Go web server utilizing the builtin templates.
The current issue I am having is that when I run the web server, it is serving the correct files but it does not load any media on the site.(Such as pictures and fonts) When I run the .html file as it is all media is loaded so I know it has something to do with my back end. Here is my code :
var templates = template.Must(template.ParseGlob("static/*.html"))
...
func index(w http.ResponseWriter, r *http.Request) {
currentTime := time.Now().Local()
toSend := payload{
Date: currentTime.Format("01-02-2006"),
Status: "Active",
}
t, err := template.ParseFiles("static/index.html")
if err != nil {
log.Fatalf("Error parsing template: %v", err)
}
t.Execute(w, toSend)
}
...
And here is my file path:
app
|-main.go
|-static(contains static files)
|-media(contains all media)
|-index.html
This serves the templates perfectly fine with all the required data yet without any media. All help is appreciated thank you!
Answered by #Adrian in the comments, I simply wasn't loading the media assets via the go server for the html to use.
http.Handle("/media/",http.StripPrefix("/media/",http.FileServer(http.Dir("static/media"))))
Was all I needed
I'm using gorilla serve mux to serve static html files.
r := mux.NewRouter()
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./public"))).Methods("GET")
I do have a Index.html file inside the public folder as well as other html files.
When browsing the site I get all the content of the folder instead of the default Index.html.
I came from C# and I know that IIS takes Index.html as default but it is possible to select any page as a default.
I wanted to know if there's a proper way to select a default page to serve in Gorilla mux without creating a custom handler/wrapper.
Maybe using a custom http.HandlerFunc would be easier:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Here you can check if path is empty, you can render index.html
http.ServeFile(w, r, r.URL.Path)
})
You do have to make a custom handler because you want a custom behavior. Here, I just wrapped the http.FileServer handler.
Try this one:
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
handler := mux.NewRouter()
fs := http.FileServer(http.Dir("./public"))
handler.PathPrefix("/").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
//your default page
r.URL.Path = "/my_default_page.html"
}
fs.ServeHTTP(w, r)
})).Methods("GET")
log.Fatal(http.ListenAndServe(":8080", handler))
}
So, from the code, if the visited path is the root (/) then you rewrite the r.URL.Path to your default page, in this case my_default_page.html.
After grabthefish mentioned it i decided to check the actual code of gorilla serve mux.
This code is taken from net/http package which Gorilla mux is based on.
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string,
redirect bool) {
const indexPage = "/index.html"
// redirect .../index.html to .../
// can't use Redirect() because that would make the path absolute,
// which would be a problem running under StripPrefix
if strings.HasSuffix(r.URL.Path, indexPage) {
localRedirect(w, r, "./")
return
}
the code request the index file to be index.html in lower case so renaming my index file solved it.
thank you grabthefish!
I use Gorilla sessions (via negroni-sessions) to store my user sessions in cookies. I also use github.com/unrolled/render for my HTML template rendering:
main.go:
package main
import (
...
"github.com/codegangsta/negroni"
"github.com/goincremental/negroni-sessions"
"github.com/goincremental/negroni-sessions/cookiestore"
"github.com/julienschmidt/httprouter"
"github.com/unrolled/render"
...
)
func init() {
...
ren = render.New(render.Options{
Directory: "templates",
Layout: "layout",
Extensions: []string{".html"},
Funcs: []template.FuncMap{TemplateHelpers},
IsDevelopment: false,
})
...
}
func main() {
...
router := httprouter.New()
router.GET("/", HomeHandler)
// Add session store
store := cookiestore.New([]byte("my password"))
store.Options(sessions.Options{
//MaxAge: 1200,
Domain: "",
Path: "/",
})
n := negroni.New(
negroni.NewRecovery(),
sessions.Sessions("cssession", store),
negroni.NewStatic(http.Dir("../static")),
)
n.UseHandler(router)
n.Run(":9000")
}
As you can see above, I use a layout.html master HTML template which is included when any page renders, like my home page:
package main
import (
"html/template"
"github.com/julienschmidt/httprouter"
)
func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var model = struct {
CatalogPicks []PromotionalModelList
ClearanceItems []Model
}{
CatalogPicks: GetCatalogPicks(),
ClearanceItems: GetClearanceItems(),
}
ren.HTML(w, http.StatusOK, "home", model)
}
In my layout.html master HTML template, I want to render an admin menu but only if the current user is an admin:
layout.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{ template "title" . }}</title>
...
</head>
<body>
...
<!--Main Menu-->
<nav class="menu">
<ul class="catalog">
<li class="has-submenu">
{{ RenderMenuCategories }}
</li>
<li>Blog</li>
<li>Company</li>
{{ RenderAdminMenu }}
</ul>
</nav>
...
My issue is that the above template helper function RenderAdminMenu() doesn't have access to the HTTP Request object and therefore cannot access the User session object to determine if the user is admin.
I can pass the User object into the template context via the Home page handler, and use an if statement RenderAdminMenu() function, like this
{{ if .User.IsAdmin }}
{{ RenderAdminMenu }}
{{ end }}
...but since I am using a master template, I would have to do that from every web page on the site. Is there a more efficient way?
I was thinking perhaps there might be a way to access some kind of global Context object from within RenderAdminMenu() (or layout.html) which contains the Request details (like you can in ASP.NET)
There's a few things you need to do to tie this together. I'm not going to show a complete example as it would be both fairly lengthy and may not match your code (which you haven't posted). It will contain the basic building blocks though: if you get stuck, come back with a direct question and a code snippet and you'll get a more direct answer :)
Write some middleware or logic in a [login] handler that saves the user data in the session when a user logs in. A userID, email and admin boolean value would be sufficient. e.g.
// In your login handler, once you've retrieved the user &
// matched their password hash (scrypt, of course!) from the DB.
session.Values["user"] = &youruserobject
err := session.Save(r, w)
if err != nil {
// Throw a HTTP 500
}
Note: remember that you need to gob.Register(&youruserobject{}) as per the gorilla/sessions docs if you want to store your own types.
Write a helper to type-assert your type when you pull it out of the session, e.g.
var ErrInvalidUser= errors.New("invalid user stored in session")
func GetUser(session *sessions.Session) (*User, error) {
// You can make the map key a constant to avoid typos/errors
user, ok := session.Values["user"].(*User)
if !ok || user == nil {
return nil, ErrInvalidUser
}
return user, nil
}
// Use it like this in a handler that serves user content
session, err := store.Get("yoursessionname", r)
if err != nil {
// Throw a HTTP 500
}
user, err := GetUser(session)
if err != nil {
// Re-direct back to the login page or
// show a HTTP 403 Forbidden, etc.
}
Write something to check if the returned user is an admin:
func IsAdmin(user *User) bool {
if user.Admin == true && user.ID != "" && user.Email != "" {
return true
}
return false
}
Pass that to the template:
err := template.Execute(w, "sometemplate.html", map[string]interface{}{
"admin": IsAdmin(user),
"someotherdata": someStructWithData,
}
// In your template...
{{ if .admin }}{{ template "admin_menu" }}{{ end }}
Also make sure you're setting an authentication key for your session cookies (read the gorilla docs), preferably an encryption key, and that you're serving your site over HTTPS with the Secure: true flag set as well.
Keep in mind that the above method is also simplified: if you de-flag a user as admin in your DB, the application will continue to detect them as an administrator for as long as their session lasts. By default this can be 7 days, so if you're in a risky environment where admin churn is a real problem, it may pay to have really short sessions OR hit the DB inside the IsAdmin function just to be safe. If it's a personal blog and it's just you, not so much.
Added: If you want to pass the User object directly to the template, you can do that too. Note that it's more performant to do it in your handler/middleware than it is in the template logic. You also get the flexibility of more error handling, and the option of "bailing out" earlier - i.e. if the session contains nothing, you can fire up a HTTP 500 error rather than rendering half a template or having to put lots of logic in your template to handle nil data.
You still need to store your User object (or equivalent) in the session, and retrieve it from session.Values before you can pass it to the template.
func GetUser(r *http.Request) *User {
session, err := store.Get("yoursessionname", r)
if err != nil {
// Throw a HTTP 500
}
if user, ok := session.Values["user"].(*User); ok {
return user
}
return nil
}
// In the handler itself
err := template.Execute(w, "sometemplate.html", map[string]interface{}{
"user": GetUser(r),
"someotherdata": someStructWithData,
}
// In your template...
{{ if .User.admin }}{{ template "admin_menu" }}{{ end }}
It seems you cannot access the Request context from a template or a template helper function afterall (so I accepted the answer above). My solution was to create a Page struct that I pass as the context for every template. It contains the Content as a generic interface, the User object, as well as other useful parameters:
//Page holds the model to be rendered for every HTTP handler.
type Page struct {
MetaTitle string
User User
HeaderStyles string
HeaderScripts string
FooterScripts string
Content interface{}
}
func (pg *Page) Init(r *http.Request) {
if pg.MetaTitle == "" {
pg.MetaTitle = "This is the default <title> content for the page!"
}
user, _ := GetUserFromSession(r)
pg.User = *user
if user.IsAdmin() {
pg.HeaderStyles += `<link href="/css/libs/summernote/summernote.css" rel="stylesheet">`
pg.FooterScripts += `<script src="/js/libs/summernote/summernote.min.js"></script>`
}
}
The Init method allows me to set defaults and use the Page struct more easily, by specifying only the Content for the page, if that's all I need:
package main
import (
"html/template"
"github.com/julienschmidt/httprouter"
)
func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var model = struct {
CatalogPicks []PromotionalModelList
ClearanceItems []Model
}{
CatalogPicks: GetCatalogPicks(),
ClearanceItems: GetClearanceItems(),
}
pg := &Page{Content: model}
pg.Init(r)
ren.HTML(w, http.StatusOK, "home", pg)
}