如何为所有页面提供静态文件,而不仅仅是几个

Posted

技术标签:

【中文标题】如何为所有页面提供静态文件,而不仅仅是几个【英文标题】:How to serve static files for all pages, not just a few 【发布时间】:2019-05-05 20:45:04 【问题描述】:

我的 Go 项目有一个问题,其中一条路线处理 CSS 很好,而另一条路线的 CSS 损坏了。 CSS 过去在两个页面上都可以工作,但现在无法为 /login.html 加载。

我知道我正确地去除了 /static/ 文件夹的前缀,因为它只在一个地方工作,而不是另一个地方。我还直接将工作页面的标题代码复制并粘贴到了非工作页面(注意使用正确的css文件)。

Negroni 显示应用程序正在调用正确的位置:

999.3µs | localhost:8080 | GET /static/css/splash.css

正确工作的 html 文件 index.html:

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Pando</title>
    <link rel="stylesheet" href="/static/css/index.css" type="text/css" />
    <link href="https://fonts.googleapis.com/css?family=Bitter|Nunito:400,700" rel="stylesheet">
</head>

<body>

    <div id="sidebar">
        <p id="logo"><img src="/static/img/logo.svg" >Pando</p>
        <span id="all-files" class="selected">All Files</span>
        <p id="shared-collections">Shared Collections<img src="/static/img/gear.svg" /></p>
        <div class="collections">
            <span>Collection 1</span>
            <span>Collection 2</span>
        </div>
        <p id="my-collections">My Collections<img src="/static/img/gear.svg" /></p>
        <div class="collections">
            <span>Collection 1</span>
            <span>Collection 2</span>
        </div>
    </div>

    <div id="header">
        <input type="button" id="upload-launch-button" value="Upload" onclick="showUploadDialog()"></button>


        <form id="search">
            <input type="search" placeholder="Search..">
            <input type="button"><img src="/static/img/search.svg"></button>
        </form>

        <div id="user">
            <img src="/static/img/user.svg">.User<a href="/logout">(Log out)</a>
        </div>
    </div>

   <!-- <span id="filter">Latest Files</span> -->

    <div id="results">
        range .Files
        <div class="img-container">
            <img src="/files/.Name" id="file-.PK">
            <div class="hover-actions">
                <a href="/files/.Name" download><img src="/static/img/download.svg"></a>
                <img src="/static/img/edit.svg">
                <img src="/static/img/delete.svg" onclick="deleteFile('/files/.Name', .PK)">
            </div>
        </div>
        end
    </div>

    <div class="dialog" id="upload-dialog">
        <div class="dialog-name">Upload</div>
        <form id="upload" enctype="multipart/form-data" action="/upload" method="post">
            <input type="file" id="selectedFile" name="file" /> <!--multiple later display none-->
            <input id="upload-button" type="submit" value="Upload" onclick="hideUploadDialog()" />
        </form>
    </div>

    <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <script type="text/javascript" src="/static/js/script.js"></script>

</body>

</html>

登录.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Pando</title>
    <link rel="stylesheet" href="/static/css/splash.css" type="text/css" />
    <link href="https://fonts.googleapis.com/css?family=Bitter|Nunito:400,700" rel="stylesheet">
</head>
<body>
    <section class="section-a">
            <div id="logo"><img src="/static/img/logo.svg">Pando</div>
            <p id="welcome">Join Pando.</p>
            <div id="buttoncont">
                <a href="/static/html/index.html"><span id="enter" class="button">Enter</span></a>
            </div>
            </section>
    <section class="section-b">
        <form id="login-form">
            <div>
                <label>Email</label><input type="email" name="username" required>
            </div>
            <div>
                <label>Password</label><input type="password" name="password" required>
            </div>
            <div>
                <input type="submit" value="Register" name="register">
                <input type="submit" value="Log In" name="login">
            </div>
            <div id="error">.Error</div>
        </form>
    </section>
</body>
</html>

完整的 go 文件:

package main

import (
    "database/sql"
    "fmt"
    "html/template"
    "io/ioutil"
    "os"
    "path/filepath"
    "strconv"
    "time"

    "net/http"

    "gopkg.in/gorp.v1"

    _ "github.com/go-sql-driver/mysql"

    "encoding/json"

    "golang.org/x/crypto/bcrypt"

    sessions "github.com/goincremental/negroni-sessions"
    "github.com/goincremental/negroni-sessions/cookiestore"
    gmux "github.com/gorilla/mux"
    "github.com/urfave/negroni"
)

/*File struct
PK primary key
Name is the original name of the file; new file location at /files/name
File Type is the extension of the file; valid file types are image formats, PDF, .AI, .PSD, and MS Word docs
Upload Date is a static value indicating when the file was uploaded
Last Modified records if any changes are made to the file while it's on the server
User is the uploading user

Eventually I will probably want to refactor this so that I can allow for files with the same name to coexist. Not sure how to do that right now elegantly.
*/
type File struct 
    PK           int64  `db:"pk"`
    Name         string `db:"name"`
    FileType     string `db:"type"`
    UploadDate   string `db:"uploadtime"`
    LastModified string `db:"modtime"`
    User         string `db:"user"`


// Tag struct
type Tag struct 
    PK     int64  `db:"pk"`
    FilePK int64  `db:"filepk"`
    Name   string `db:"name"`


// Collection struct
type Collection struct 
    PK          int64  `db:"pk"`
    Name        string `db:"name"`
    ContentName string `db:"contentname"`
    ContentType string `db:"type"`


// User struct
type User struct 
    Username string `db:"username"`
    Secret   []byte `db:"secret"`


// Page struct
type Page struct 
    Files  []File
    Filter string
    User   string


// LoginPage struct
type LoginPage struct 
    Error string


// UploadPage struct
type UploadPage struct 
    Error string


var db *sql.DB
var dbmap *gorp.DbMap

func main() 

    initDb()
    index := template.Must(template.ParseFiles("html/index.html"))
    login := template.Must(template.ParseFiles("html/login.html"))
    upload := template.Must(template.ParseFiles("html/upload.html"))

    mux := gmux.NewRouter()
    defer db.Close()

    mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
    mux.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("css"))))
    mux.PathPrefix("/img/").Handler(http.StripPrefix("/img/", http.FileServer(http.Dir("img"))))
    mux.PathPrefix("/files/").Handler(http.StripPrefix("/files/", http.FileServer(http.Dir("files"))))

    // Login
    mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) 
        var p LoginPage

        if r.FormValue("register") != "" 
            secret, _ := bcrypt.GenerateFromPassword([]byte(r.FormValue("password")), bcrypt.DefaultCost)
            user := Userr.FormValue("username"), secret
            if err := dbmap.Insert(&user); err != nil 
                p.Error = err.Error()
             else 
                sessions.GetSession(r).Set("User", user.Username)
                http.Redirect(w, r, "/", http.StatusFound)
                return
            
         else if r.FormValue("login") != "" 
            user, err := dbmap.Get(User, r.FormValue("username"))
            if err != nil 
                p.Error = err.Error()
             else if user == nil 
                p.Error = "No user account exists for the username " + r.FormValue("username")
             else 
                u := user.(*User)
                if err = bcrypt.CompareHashAndPassword(u.Secret, []byte(r.FormValue("password"))); err != nil 
                    p.Error = err.Error()
                 else 
                    sessions.GetSession(r).Set("User", u.Username)
                    http.Redirect(w, r, "/", http.StatusFound)
                    return
                
            
        

        if err := login.ExecuteTemplate(w, "login.html", p); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
        

    )

    // Upload
    mux.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) 

        var p UploadPage

        // Checks filesize against max upload size (10MB)
        if err := r.ParseMultipartForm(10 << 20); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        

        // reads file
        fileType := r.PostFormValue("type")
        file, header, err := r.FormFile("file")
        if err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        

        defer file.Close()
        fileBytes, err := ioutil.ReadAll(file)
        if err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        

        // checks the filetype against expected mime types
        mimetype := http.DetectContentType(fileBytes)
        if mimetype != "image/jpeg" && mimetype != "image/jpg" &&
            mimetype != "image/gif" && mimetype != "image/png" &&
            mimetype != "application/pdf" && mimetype != "image/vnd.adobe.photoshop" && mimetype != "application/illustrator" && mimetype != "image/vnd.microsoft.icon" &&
            mimetype != "application/msword" && mimetype != "application/x-photoshop" && mimetype != "application/photoshop" && mimetype != "application/psd" 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        

        filename := header.Filename
        newPath := filepath.Join("files/", filename)
        fmt.Printf("FileType: %s, File: %s\n", fileType, newPath)

        t := time.Now().String()
        currentTime, _ := time.Parse(time.Stamp, t)

        // Creates a File struct-type object out of the file information from
        f := File
            PK:           -1,
            Name:         filename,
            FileType:     fileType,
            UploadDate:   currentTime.String(),
            LastModified: currentTime.String(),
            User:         getStringFromSession(r, "User"),
        

        // Inserts the file information into the database
        if err = dbmap.Insert(&f); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        

        newFile, err := os.Create(newPath)
        if err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        

        defer newFile.Close()
        if _, err := newFile.Write(fileBytes); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        
        // w.Write([]byte("SUCCESS"))

        if err := upload.ExecuteTemplate(w, "upload.html", p); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
        

    ).Methods("POST")

    // Sort
    mux.HandleFunc("/files", func(w http.ResponseWriter, r *http.Request) 

        var b []File
        if !getFileCollection(&b, r.FormValue("sortBy"), getStringFromSession(r, "Filter"), getStringFromSession(r, "User"), w) 
            return
        

        sessions.GetSession(r).Set("sortBy", r.FormValue("sortBy"))

        if err := json.NewEncoder(w).Encode(b); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        

    ).Methods("GET").Queries("sortBy", "sortBy:title|author|classification")

    // Default page
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) 

        p := PageFiles: []File, Filter: getStringFromSession(r, "Filter"), User: getStringFromSession(r, "User")
        if !getFileCollection(&p.Files, getStringFromSession(r, "SortBy"), getStringFromSession(r, "Filter"), p.User, w) 
            return
        

        if err := index.ExecuteTemplate(w, "index.html", p); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
        

    ).Methods("GET")

    mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) 
        sessions.GetSession(r).Set("User", nil)
        sessions.GetSession(r).Set("Filter", nil)

        http.Redirect(w, r, "/login", http.StatusFound)
    )

    // Deletes file from database; currently not working :(
    mux.HandleFunc("/files/name", func(w http.ResponseWriter, r *http.Request) 

        pk, _ := strconv.ParseInt(gmux.Vars(r)["pk"], 10, 64)

        fmt.Printf("pk is %d", pk)

        var f File
        if err := dbmap.SelectOne(&f, "select * from files where pk=?", pk); err != nil 
            http.Error(w, err.Error(), http.StatusBadRequest)
        
        if _, err := dbmap.Delete(&f); err != nil 
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        
        w.WriteHeader(http.StatusOK)

    ).Methods("DELETE")

    // Session management
    n := negroni.Classic()
    n.Use(sessions.Sessions("pando", cookiestore.New([]byte("hubert88"))))
    n.Use(negroni.HandlerFunc(verifyDatabase))
    n.Use(negroni.HandlerFunc(verifyUser))
    n.UseHandler(mux)
    n.Run(":8080")

 // end main

// Opens the database connection to SQL and creates tables if they don't exist
func initDb() 
    db, _ = sql.Open("mysql", "root:secret@tcp(127.0.0.1:3306)/pando")

    dbmap = &gorp.DbMapDb: db, Dialect: gorp.MySQLDialect"InnoDB", "UTF8"

    // creates tables, specifies the fields on the struct that map to table primary keys
    dbmap.AddTableWithName(File, "files").SetKeys(true, "pk")
    dbmap.AddTableWithName(Tag, "tags").SetKeys(true, "pk")
    dbmap.AddTableWithName(Collection, "collections").SetKeys(true, "pk")
    dbmap.AddTableWithName(User, "users").SetKeys(false, "username")
    dbmap.CreateTablesIfNotExists()



// Checks to make sure the database is alive
func verifyDatabase(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) 
    if err := db.Ping(); err != nil 
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    
    next(w, r)


func getStringFromSession(r *http.Request, key string) string 
    var strVal string
    if val := sessions.GetSession(r).Get(key); val != nil 
        strVal = val.(string)
    
    return strVal


func verifyUser(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) 

    if r.URL.Path == "/login" 
        next(w, r)
        return
    

    if username := getStringFromSession(r, "User"); username != "" 
        if user, _ := dbmap.Get(User, username); user != nil 
            next(w, r)
            return
        
    
    http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)



我正在努力调试这个,我得到的所有搜索结果都只谈论剥离前缀(我已经在做)。

在我的浏览器的网络选项卡中,CSS 和图像文件返回 307 临时重定向错误。

运行 colminator 的 curl 命令给出以下输出:

HTTP/1.1 307 Temporary Redirect
Content-Type: text/html; charset=utf-8
Location: /login
Date: Sun, 05 May 2019 22:16:17 GMT
Content-Length: 42

这就是我处理静态文件的方式。

mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

【问题讨论】:

检查浏览器中的网络选项卡,并使用对在每种情况下已执行的 css 文件的确切请求更新问题,包括 http 状态代码。 我已经更新了我的问题。显然发生了 307 临时重定向 - 但我无法想象为什么。 你的意思是在这两个页面上加载不同的css文件吗?还是应该是同一个? 故意是两个不同的css文件。 splash.css 上的权限是否足够开放,运行 go-server 的进程可以读取它?执行针对此 css 文件的 curl 命令 - 以查看任何其他标头信息,例如curl http://localhost:8080/static/css/splash.css -D /dev/tty 【参考方案1】:

正如 cmets 所暗示的,另一个处理程序正在拦截您的静态文件路由。尝试简化您的路线。减少:

mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("css"))))
mux.PathPrefix("/img/").Handler(http.StripPrefix("/img/", http.FileServer(http.Dir("img"))))
mux.PathPrefix("/files/").Handler(http.StripPrefix("/files/", http.FileServer(http.Dir("files"))))

到单条路线:

mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

并将所有静态文件移动到 static 目录下 - 相应地更新任何 html/css 路径。

【讨论】:

以上是关于如何为所有页面提供静态文件,而不仅仅是几个的主要内容,如果未能解决你的问题,请参考以下文章

如何为静态文件设置 http 标头?

如何为查看基于 Amazon S3 的静态网站的用户提供目录列表?

Erlang Cowboy如何为静态文件添加响应头

如何为自定义JVM语言实现静态代码分析工具的类型信息?

如何为linux静态库隐藏本地符号

如何为 WPF 故事板中的静态对象设置动画