为什么我的脚本读取链​​接在我的 HTML 中的文件,而在 GoLang 中使用 ioutil.ReadFile() 读取时未指定这些文件?

Why is my script reading in files linked in my HTML which aren't being specified when reading in with ioutil.ReadFile() in GoLang?

我有一个 GoLang 脚本,用于根据浏览器中的输入查询动态构建网页,如下所示 http://localhost:8000/blog/post#post# 部分用于识别哪个 JSON 数据文件解析为我创建的 HTML 模板;例如,如果我输入 http://localhost:8000/blog/post1,那么它会从我的 post1.json 文件中创建一个 index.html 文件。目前,我的脚本 运行 允许我在浏览器退出之前在我的浏览器中加载单个页面,并在我的终端标准输出日志中显示错误:

2022/03/18 00:32:02 error: open jsofun.css.json: no such file or directory
exit status 1

这是我当前的脚本:

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))
}

我已经进行了一些基本的调试,并确定问题出在以下几行:

json_file, err := ioutil.ReadFile(blogstr)
    if err != nil {
        log.Fatal("error: ", err)
    }

让我感到困惑的是,为什么 ioutil.ReadFile 也在尝试读取我的 HTML 中链接的文件?浏览器不应该处理该链接而不是我的处理程序吗?作为参考,这是我的 HTML 链接文件 jsofun.css 的地方;我的控制台输出中引用的错误显示我的脚本在调用 ioutil.ReadFile:

期间试图以 jsofun.css.json 访问此文件
<!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>

我知道我已将 .json 添加到行中的查询字符串:blogstr = blogstr + ".json",但是,我不明白为什么这也会影响我的 HTML 中的链接.有没有我遗漏的东西,或者为什么它会阅读我的 HTML 模板中的链接?我想让 ioutil.ReadFile 做的就是读取我的 JSON 文件,该文件已解析到我的模板中。

编辑:我想补充一点,当我最初将页面加载到浏览器中时,它成功加载了我 JSON 中的文本内容,但没有任何样式。当我查看源代码时,链接也是正确的,这让我感到困惑,为什么样式表不适用,因为它位于正确的目录中,可以访问。这是我 select 在浏览器中查看源代码时的源代码:

<!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>

作为参考,这是我的 JSON 文件的示例:

{
    "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."
}

您的 Go 服务器设置为 服务于 /blog/ 路径,它通过执行 blogHandler 来实现。您的 Go 服务器中没有其他任何东西被设置为提供 css、js 或图像文件等资产。

对于这样的事情,您通常需要在单独的路径上注册一个单独的 FileServer 处理程序。示例:

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))
}

您需要修复的另一件事是 HTML 中那些资产字段的链接,它们应该是绝对的,而不是相对的。

...
<link rel="stylesheet" href="/assets/jsofun.css"></style>
...
<script src="/assets/jsofun.js">

只有当资源位于 /path/to/assets/on/my/computer 目录中时,以上当然才有效,例如

/path/to/assets/on/my/computer
├── jsofun.css
└── jsofun.js

blogHandler 不必要地为每个请求创建一个新文件而不删除它,这有可能很快将您的磁盘填满到其最大容量。要提供模板,您不需要创建新文件,而是可以直接在 http.ResposeWriter 中执行模板。还建议只解析一次模板,尤其是在生产代码中,以避免不必要的资源浪费:

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)
    }
}

让我们研究一下当您请求 http://localhost:8000/blog/post#.

时会发生什么

浏览器请求页面;您的代码成功构建并且 returns 一些 html - 这将包括:

<link rel="stylesheet" href="./jsofun.css"></style>

浏览器接收并处理HTML;作为该过程的一部分,它请求上面的 css。现在原始请求位于文件夹 /blog/post# 中,因此 ./jsofun.css 变为 http://localhost:8000/blog/jsofun.css.

当您的 go 应用程序收到此请求时 blogHandler 将被调用(由于请求路径);它去掉 /blog/ 然后添加 .json 以获得文件名 jsofun.css.json。然后您尝试打开此文件并收到错误消息,因为它不存在。

有几种方法可以解决这个问题;更改模板以使用 <link rel="stylesheet" href="/jsofun.css"></style> 可能是一个开始(但我不知道 jsofun.css 存储在哪里,并且您没有显示任何可用于该文件的代码)。我认为还值得注意的是,您不必在磁盘上创建文件 index.html(除非有其他原因需要这样做)。

(请参阅 mkopriva 对其他问题和进一步步骤的回答 - 在发布该答案时输入此内容已经进行了一半,并且觉得演练可能仍然有用)。