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

标签 go mux

我的 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" height="14px">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 := User{r.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 := Page{Files: []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.DbMap{Db: 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"))))

最佳答案

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

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 路径。

关于go - 如何为所有页面提供静态文件,而不仅仅是少数几个,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55996263/

相关文章:

Go using mux Router - 如何将我的数据库传递给我的处理程序

go - 如何将嵌套结构传递给参数

戈朗 : how to cast []string to a typed struct?

使用 for 循环和匿名函数进行并发时出现意外行为

go - 默认服务器 mux 如何匹配 url 模式

url 的 Gorilla Mux 正则表达式与完整 url 不匹配

compiler-errors - 编译错误2 :1 MUX with fault handling using individual components

go - 如何使用 Colly 在 Golang 中将字符串解析为 DOM 元素

json - 从 JSON 反序列化时接口(interface)断言失败

go - 使用go语言创建异步tcp服务器