如何为所有页面提供静态文件,而不仅仅是几个
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 路径。
【讨论】:
以上是关于如何为所有页面提供静态文件,而不仅仅是几个的主要内容,如果未能解决你的问题,请参考以下文章