How do I remove the index.html from my URL bar e.g. localhost:8000/index.html
package main
import (
"net/http"
"io/ioutil"
)
func main() {
http.Handle("/", new(MyHandler))
http.ListenAndServe(":8000", nil)
}
type MyHandler struct {
http.Handler
}
func (this *MyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := "public" + req.URL.Path
data, err := ioutil.ReadFile(string(path))
if err == nil {
w.Write(data)
} else {
w.WriteHeader(404)
w.Write([]byte("404 - " + http.StatusText(404)))
}
}
Add a condition to serve index.html if the URL path is empty:
path := "public"
if req.URL.Path == "/" {
path += "/index.html"
} else {
path += req.URL.Path
}
Also, it would be a good idea to use net/http.ServeFile over manually writing the data to the output stream (see net/http#ServeContent's documentation to learn why this is a good idea).
It's also worth noting that a built-in handler for serving files exists.
Related
How can I server static files (with FileServer) for a virtual host in Go?
If I have my own handler function, the task seems to be easily solvable [1]:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, world!")
})
http.HandleFunc("qa.example.com/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, improved world!")
})
http.ListenAndServe(":8080", nil)
}
But what if I need to serve static files (with FileServer)
for a virtual host?
This
r.PathPrefix("qa.example.com/").Handler(http.FileServer(http.Dir("/static/qa/")))
does not work — it is just ignored.
What am I doing wrong?
Is this approach generally wrong?
package main
import (
"embed"
"fmt"
"net/http"
"strings"
"time"
)
//go:embed *.go
var f embed.FS
func main() {
// embed.Fs defaule modtime use now or env value.
now := time.Now()
// use mapping host to http.FileSystem
vhosts := make(map[string]http.FileSystem)
vhosts["qa.example.com"] = http.FS(f) // from embed.FS
vhosts["my.example.com"] = http.Dir(".") // from http.Dir
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, world!")
})
http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
// find host
fs, ok := vhosts[r.Host]
if !ok {
w.WriteHeader(404)
w.Write([]byte("404 not found vhost"))
return
}
// open file from http.FileSystem
file, err := fs.Open(strings.TrimPrefix(r.URL.Path, "/static/"))
if err != nil {
// reference go1.18.3/net/http/fs.go toHTTPError function hander file error.
w.Write([]byte("check err is 403 or 404 or 500"))
return
}
stat, _ := file.Stat()
// fix embed modtime is zero.
modtime := stat.ModTime()
if modtime.IsZero() {
modtime = now
}
// response
http.ServeContent(w, r, stat.Name(), modtime, file)
})
http.ListenAndServe(":8080", nil)
}
run test exec command curl -H "Host: my.example.com" 127.0.0.1:8080/static/01.go, 01.go replacte your static filename.
Register a handler for host/path. Strip the /path part only when invoking the file handler.
This registration serves files for qa.example.com/static/* from the directory ./static/qa/.
http.HandleFunc("qa.example.com/static/", http.StripPrefix("/static", http.FileServer(http.Dir("./static/qa/")))
func main() {
mux := http.NewServeMux()
staticHandler := http.FileServer(http.Dir("./templates"))
mux.Handle("/", http.StripPrefix("/", staticHandler))
log.Fatal(http.ListenAndServe(":8080", mux))
}
I want to load a html file which is in 'templates' directory.
if there are more than one files in 'templates', how can I select certain file to load?
You can use http.ServeFile() to build your own file server.
See sketch below.
Then you can intercept the served files within your custom fileHandler.ServeHTTP().
package main
import (
"log"
"net/http"
"path"
"path/filepath"
"strings"
)
func main() {
mux := http.NewServeMux()
//staticHandler := http.FileServer(http.Dir("./templates"))
staticHandler := fileServer("./templates")
mux.Handle("/", http.StripPrefix("/", staticHandler))
log.Printf("listening")
log.Fatal(http.ListenAndServe(":8080", mux))
}
// returns custom file server
func fileServer(root string) http.Handler {
return &fileHandler{root}
}
// custom file server
type fileHandler struct {
root string
}
func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
upath := r.URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
r.URL.Path = upath
}
name := filepath.Join(f.root, path.Clean(upath))
log.Printf("fileHandler.ServeHTTP: path=%s", name)
http.ServeFile(w, r, name)
}
I want to make it so when someone visits a page on my Go HTTP server, they won't see the .html extension.
E.g. when they visit https://example.org/test they will see the content of https://example.org/test.html.
My code:
package main
import (
"net/http"
)
func main() {
fs := http.FileServer(http.Dir("public/"))
http.Handle("/", http.StripPrefix("/", fs))
http.ListenAndServe(":8000", nil)
}
One option is to implement http.FileSystem using http.Dir. The advantage of this approach is that it takes advantage of the carefully written code in http.FileServer.
It will look something like this:
type HTMLDir struct {
d http.Dir
}
func main() {
fs := http.FileServer(HTMLDir{http.Dir("public/")})
http.Handle("/", http.StripPrefix("/", fs))
http.ListenAndServe(":8000", nil)
}
The implementation of the Open method depends on the application requirements.
If you always want to tack on the .html extension, then use this code:
func (d HTMLDir) Open(name string) (http.File, error) {
return d.d.Open(name + ".html")
}
If you want to fallback to the .html extension, then use this code:
func (d HTMLDir) Open(name string) (http.File, error) {
// Try name as supplied
f, err := d.d.Open(name)
if os.IsNotExist(err) {
// Not found, try with .html
if f, err := d.d.Open(name + ".html"); err == nil {
return f, nil
}
}
return f, err
}
Flip the previous one around to start with the .html extension and fallback to the name as supplied:
func (d HTMLDir) Open(name string) (http.File, error) {
// Try name with added extension
f, err := d.d.Open(name + ".html")
if os.IsNotExist(err) {
// Not found, try again with name as supplied.
if f, err := d.d.Open(name); err == nil {
return f, nil
}
}
return f, err
}
So basically you want the http.FileServer functionality, but you don't want clients having to enter the trailing .html extension.
Another simple solution is to add it yourself, at the server side. This is how it can be done:
fs := http.FileServer(http.Dir("public"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
r.URL.Path += ".html"
fs.ServeHTTP(w, r)
})
panic(http.ListenAndServe(":8000", nil))
And that's all.
If you want this file server to also serve other files (such as images and CSS files), only append the .html extension if it has no extension:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if ext := path.Ext(r.URL.Path); ext == "" {
r.URL.Path += ".html"
}
fs.ServeHTTP(w, r)
})
It's possible, but it cannot be done by serving the files using http.FileServer().
Instead, create a custom handler for the / route. Inside the handler, serve the requested file directly using http.ServeFile().
viewPath := "public/"
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// hack, if requested url is / then point towards /index
if r.URL.Path == "/" {
r.URL.Path = "/index"
}
requestedPath := strings.TrimLeft(filepath.Clean(r.URL.Path), "/")
filename := fmt.Sprintf("%s/%s.html", viewPath, requestedPath)
http.ServeFile(w, r, filename)
}))
http.ListenAndServe(":8000", nil)
The .html suffix is added to every request path, so it'll point correctly towards the html files.
path / -> ./public/index.html
path /index -> ./public/index.html
path /some/folder/about -> ./public/some/folder/about.html
...
Problem:
Unable to access mux.CurrentRoute(r).GetName() from middleware. (Although I had been able to access it from my middleware, I had to change the way my middleware works due to it's previous inability to access the request). So I've mucked something up and I'm not sure how to get back to a working state where I can access the route name.
Any help would be much appreciated!
Error:
runtime error: invalid memory address or nil pointer dereference
Code:
func main() {
var (
err error
r *mux.Router
devRouter *mux.Router
usersRouter *mux.Router
brandsRouter *mux.Router
)
defer db.Close()
defer store.Close()
r = mux.NewRouter()
devRouter = r.PathPrefix("/api/v1/dev").Subrouter()
usersRouter = r.PathPrefix("/api/v1/users").Subrouter()
brandsRouter = r.PathPrefix("/api/v1/brands").Subrouter()
// development endpoints
devRouter.HandleFunc("/db/seed", devDbSeed)
...
// users
usersRouter.HandleFunc("/create", usersCreateHandlerFunc).Methods("POST").Name("USERS_CREATE")
...
// brands
brandsRouter.HandleFunc("/create", brandsCreateHandlerFunc).Methods("POST").Name("BRANDS_CREATE")
...
// products
brandsRouter.HandleFunc("/{brand_id:[0-9]+}/products", brandsProductsListHandlerFunc).Methods("GET").Name("BRANDS_PRODUCTS_LIST")
...
// mwAuthorize and mwAuthenticate basically work the same
mw := []func(http.Handler) http.Handler{mwAuthenticate, mwAuthorize}
http.Handle("/", use(r, mw...))
err = http.ListenAndServe(":9000", nil)
if err != nil {
logIt(err)
}
}
func use(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler {
// exec order: mw[0],mw[1],mw[N]...
for i := len(mw) - 1; i >= 0; i-- {
h = mw[i](h)
}
return h
}
func mwAuthorize(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if true != authorize(r) {
w.WriteHeader(http.StatusForbidden)
return
} else {
next.ServeHTTP(w, r)
}
})
}
func authorize(r *http.Request) (isAuthorized bool) {
isAuthorized = false
/**
This is where it's failing!
*/
routeName := mux.CurrentRoute(r).GetName()
switch routeName {
case "USERS_CREATE":
// route-specific authorization
break
...
default:
break
}
return
}
Update (2015-01-04 # 4:49PM EST):
So after removing the middleware (or at least commenting out the section that's trying to read mux.CurrentRoute) I am able to retrieve the route name from the destination handlerfunc (ex: usersCreateHandlerFunc or brandsCreateHandlerFunc). This doesn't solve my problem (I'd still like to perform authentication/authorization in middleware as opposed to every handlerfunc), I have a hunch it's letting me know *mux.Router isn't available in my middleware until after the final .ServeHTTP call. (Or something along those lines...)
Update (2015-01-04 # 5:41PM EST):
Tried a different (albeit less-preferred) direction of using Negroni as the middleware component. Still getting nil-pointer error when I try to get mux.CurrentRoute.
Update (2015-01-04 # 6:17PM EST):
I am able to access the request (ex: r.URL) from the middleware func's, but still no luck on accessing the mux.Route (ex: mux.CurrentRoute(r)). After looking a bit more at the mux source, I think it's because the current mux context isn't getting set because the router hasn't executed the matcher yet (and therefore it doesn't know what route it's currently on until AFTER the middleware is complete). However, I'm still not sure how to either resolve this, or re-structure my code to handle this.
What about:
routeName := mux.CurrentRoute(r).GetName()
Where r is the *http.Request. Don't forget to import "github.com/gorilla/mux". Remember that in order to use this, you must give you route a name when you define it
From CurrentRoute godoc:
CurrentRoute returns the matched route for the current request, if any. This only works when called inside the handler of the matched route because the matched route is stored in the request context[...]
In your example, your chain of mwAuthenticate, mwAuthorize is attached to the route "/" without using gorilla mux. That means when the request passes your handlers, it has not passed gorilla mux router.
Try the following (your example stripped down):
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
var (
err error
r *mux.Router
devRouter *mux.Router
)
func devDbSeed(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "devDbSeed")
return
}
func main() {
r = mux.NewRouter()
devRouter = r.PathPrefix("/api/v1/dev").Subrouter()
// mwAuthorize and mwAuthenticate basically work the same
mw := []func(http.Handler) http.Handler{mwAuthenticate, mwAuthorize}
// development endpoints
devRouter.Handle("/db/seed", use(http.HandlerFunc(devDbSeed), mw...)).Name("foo")
// Send all requests into the mux router
err = http.ListenAndServe(":9000", r)
if err != nil {
log.Fatal(err)
}
}
func use(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler {
// exec order: mw[0],mw[1],mw[N]...
for i := len(mw) - 1; i >= 0; i-- {
h = mw[i](h)
}
return h
}
func mwAuthorize(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !authorize(r) {
w.WriteHeader(http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func mwAuthenticate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}
func authorize(r *http.Request) (isAuthorized bool) {
isAuthorized = false
handlerName := "UNKNOWN"
if route := mux.CurrentRoute(r); route != nil {
routeName := route.GetName()
if routeName != "" {
handlerName = routeName
}
}
log.Println(handlerName)
switch handlerName {
case "USERS_CREATE":
// route-specific authorization
log.Println("USERS_CREATE")
break
default:
break
}
return
}
I had the same problem and I resolved in that way:
var match mux.RouteMatch
routeExists := s.Router.Match(r, &match)
if routeExists && match.Route.GetName(){
routeName := match.Route.GetName()
}
And when I defined the route I added .Name("route/:param") where route/:param is my route.
I'm trying to build a sample web application demonstrating rest techniques using go at the back-end, serving json based requests and javascript, jquery in the front-end (I'm not using html/template package).
FileServer "returns a handler that serves HTTP requests with the contents of the file system rooted at root."
supose that I'm publishing my static folder that contains index.html and scripts folder holding some javascript files.
How can I prevent the client from viewing my js files (publishing just the index.html at /) ?
You can easily restrict the FileServer, which is a HttpHandler by wrapping another HttpHandler around that. For example, take this wrapper which ONLY allows *.js files to be served:
func GlobFilterHandler(h http.Handler, pattern string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
fileName := filepath.Base(path)
if ok, err := filepath.Match(pattern, fileName); !ok || err != nil {
if err != nil {
log.Println("Error in pattern match:", err)
}
http.NotFound(w, r)
return
}
h.ServeHTTP(w, r)
})
}
func main() {
fileHandler := http.FileServer(http.Dir("/tmp/dtest"))
wrappedHandler := GlobFilterHandler(fileHandler, "*.js")
}
You can find a blog post here which describes the basic idea pretty good.
Another option you have is to extend on http.Dir and make your own http.FileSystem implementation which does exactly what you want:
type GlobDir struct {
Dir http.Dir
Pattern string
}
func (d GlobDir) Open(name string) (http.File, error) {
baseName := filepath.Base(name)
if ok, err := filepath.Match(d.Pattern, baseName); !ok || err != nil {
if err != nil {
return nil, err
}
return nil, fmt.Errorf("%s not match GlobDir pattern.", baseName)
}
return d.Dir.Open(name)
}
func main() {
fileHandler := http.FileServer(GlobDir{
Dir: http.Dir("/tmp/dtest"),
Pattern: "*.js",
})
http.ListenAndServe(":8080", fileHandler)
}
The second solution implements the http.FileSystem interface which is accepted by http.FileServer.
It checks whether the input file name matches the supplied pattern and then hands control down to the original http.Dir. This is probably the way you want to go here.