doujujian0052
2019-05-05 20:45
浏览 95
已采纳

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

I'm having a problem with my Go project where one route handles CSS fine and another route's CSS is broken. The CSS used to work on both pages, but now it isn't loading for /login.html.

I know that I'm properly stripping the prefix for the /static/ folder because it's working in once place and not another. I also directly copied and pasted the header code from the working page to the not-working page (being careful to use the correct css file).

Negroni is showing that the application is making the call to the correct location:

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

The correctly working html file 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>

Login.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>

The complete go file:

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


I'm at my wits end trying to debug this, and all the search results I get only talk about stripping prefixes (which I'm already doing).

In the Networking tab of my browser, the CSS and image files are returning a 307 Temporary Redirect error.

Running colminator's curl command gave this output:

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

This is how I'm handling my static files.

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

1条回答 默认 最新

相关推荐 更多相似问题