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:[email protected]" >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:[email protected]" >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."
}
CodePudding user response:
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)
}
}
CodePudding user response:
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).