How to server static files with virtual hosts functioality in Go - http

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/")))

Related

GOLANG How can I load a certain html file from templates directory using http.FileServer

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

Golang mux http.FileServer not returning image

I am new to go and am trying to setup a go server. My intention is to return an image when the url is hit.
this is what i have done
myRouter := mux.NewRouter()
myRouter.HandleFunc("/poster_path/{id}",posterfunc)
This is my posterfunc
func posterfunc(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "image/jpeg")
vars := mux.Vars(r)
key := vars["id"]
var url = "/home/rakshithjk/Desktop/poster/"+key+".jpg"
http.FileServer(http.Dir(url))
}
This is the output in Postman -
Any help would be appreciated.
UPDATE -
tried changing http.FileServer to http.ServeFile, but the output remains the same
Modified handler function
func posterfunc(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "image/jpeg")
vars := mux.Vars(r)
key := vars["id"]
var url = "/home/rakshithjk/Desktop/poster/"+key+".jpg"
http.ServeFile(w, r,url)
This is my entire file contents(for reference)
package main
import (
"fmt"
"log"
"net/http"
"encoding/json"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
//"github.com/davecgh/go-spew/spew"
)
func handleRequests() {
myRouter := mux.NewRouter()
myRouter.HandleFunc("/", homePage)
myRouter.HandleFunc("/movie/top_rated", returnSingleArticle)
myRouter.HandleFunc("/poster_path",posterfunc)
log.Fatal(http.ListenAndServe(":10000", myRouter))
}
func enableCors(w *http.ResponseWriter) {
(*w).Header().Set("Access-Control-Allow-Origin", "*")
}
type movie_list struct {
Page int `json:"page"`
Results []movie `json:"results"`
}
type movie struct {
Id int `json:"id"`
Title string `json:"title"`
Language string `json:"language"`
Release_date string `json:"release_date"`
Poster_path string `json:"poster_path"`
Background_path string `json:"background_path"`
Overview string `json:"overview"`
Genre_ids string `json:"genre_ids"`
}
func posterfunc(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "image/jpeg")
//vars := mux.Vars(r)
//key := vars["id"]
enableCors(&w)
var url = "/home/rakshithjk/go/src/clumio/112.png"
fmt.Fprintf(w, "Hello, %q\n", url)
http.ServeFile(w, r,url)
}
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the HomePage!")
fmt.Println("Endpoint Hit: homePage")
}
func returnSingleArticle(w http.ResponseWriter, r *http.Request) {
//vars := mux.Vars(r)
//key := vars["id"]
enableCors(&w)
db, err := sql.Open("mysql", "root:72574484#tcp(127.0.0.1:3306)/PicturePerfect")
if err != nil {
fmt.Println(err)
}else{
fmt.Println("Connection Established")
}
rows,err:=db.Query("select * from movies limit 10")
if err!=nil{
fmt.Println(err)
}
var list movie_list
var tag movie
for rows.Next(){
err:=rows.Scan(&tag.Id,&tag.Title,&tag.Language,&tag.Release_date,&tag.Poster_path,&tag.Background_path,&tag.Overview,&tag.Genre_ids)
if err != nil {
fmt.Println(err)
}
fmt.Println(tag.Id)
list.Results = append(list.Results,tag)
}
err = rows.Err()
if err != nil {
fmt.Println(err)
}
defer db.Close()
//fmt.Fprintf(w, "Hello, %q\n", list.Results[3])
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(list)
//spew.Dump(list)
//fmt.Fprintf(w, "given lamguage, %q\n", tag.Poster_path)
}
func main() {
handleRequests()
}
http.FileServer() should not be called in a function like that. It returns a Handler function (a function similar to the posterfunc you created).
It should be used as the handler function in the route configuration like this:
myRouter.HandleFunc("/poster_path/",http.FileServer(http.Dir("./your_dir")))
Here you can find the documentation for http.FileServer, with some more detailed examples.
This blog post does a fine step-by-step explanation on how to set it up.
UPDATE:
If you want to use it inside your handler, you should use http.ServeFile. It will respond to the request with the contents of the named file or directory.
Docs here
func posterfunc(w http.ResponseWriter, r *http.Request){
w.Header().Set("Content-Type", "image/jpeg")
vars := mux.Vars(r)
key := vars["id"]
var url = "/home/rakshithjk/Desktop/poster/"+key+".jpg"
http.ServeFile(w, r, url)
}
UPDATE 2:
The issue in the new snippet is that you are printing to the interface just before serving the file.
Remove this line:
fmt.Fprintf(w, "Hello, %q\n", url)

Basic Authentication for static resources

How can I add basic authentication to my static resources? With the code below, I'm able to view any files that are in the labels folder. I know in this question it was explained how to do it. But how would would I set the header when a http.ResponseWriter is not used?
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
"os"
)
func main() {
port := GetPort()
log.Println("[-] Listening on...", port)
r := mux.NewRouter()
r.PathPrefix("/labels/").Handler(http.StripPrefix("/labels/", http.FileServer(http.Dir("./labels/"))))
err := http.ListenAndServe(port, r)
log.Fatal(err)
}
// GetPort is for herkou deployment
func GetPort() string {
port := os.Getenv("PORT")
if port == "" {
port = "4747"
log.Println("[-] No PORT environment variable detected. Setting to ", port)
}
return ":" + port
}
Create a wrapper around each handler to pass the request from the authentication middleware which will forward the request further after authentication is done else return the response with error as
func authentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Executing authentication")
next.ServeHTTP(w, r)
})
}
// open the dialog to download pdf files.
func dowloadPdf(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Disposition", "attachment; filename=YOUR_FILE")
w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
w.Write([]byte("File downloaded"))
}
func main(){
pdfHandler := http.HandlerFunc(dowloadPdf)
http.Handle("/servepdf", authentication(pdfHandler))
http.ListenAndServe(":3000", nil)
}
But if I consider the fact there is no need to have authentication when serving static files like html, css, js etc. It would be better to create a handler to serve pdf files after authenticating users.
You can also use negorni middlewares with gorilla mux rather than creating custom middlewares.
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
"os"
)
func main() {
port := GetPort()
log.Println("[-] Listening on...", port)
r := mux.NewRouter()
r.PathPrefix("/labels/").Handler(http.StripPrefix("/labels/", ServeLabels(http.FileServer(http.Dir("./labels/")))))
err := http.ListenAndServe(port, r)
log.Fatal(err)
}
func ServeLabels(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="mydomain"`)
h.ServeHTTP(w, r)
})
}
// GetPort is for herkou deployment
func GetPort() string {
port := os.Getenv("PORT")
if port == "" {
port = "4747"
log.Println("[-] No PORT environment variable detected. Setting to ", port)
}
return ":" + port
}
something like this, or you could just go ahead and use the gorilla mux middleware.

Gorilla mux, best way to 'catch' response codes

I'm using Gorilla mux for all my routing. Now my app is working fine, I want to find a way to log all my response codes to -for example- statds. I have found this package: https://godoc.org/github.com/gorilla/handlers#LoggingHandler
Which allows me to output all responses into apache format. Although this is nice, it's not 100% what I want. I just want to extract the response statusses and send them to statds. Now what's the best/easiest way to achieve this?
package main
import (
"log"
"net/http"
"os"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/rogierlommers/mux-status-handler/articles"
"github.com/rogierlommers/mux-status-handler/users"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/products", articles.Handler)
r.HandleFunc("/users", users.Handler)
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
log.Println("listening on 8080")
http.ListenAndServe(":8080", loggedRouter)
}
Above code gives me this:
So I'm looking for something similar, but instead of outputting the Apache access logs to stdout, I would like to be able to "do something" with the response code. I have also created a simple repo which contains my sample code. You can find it here.
I found this useful Blog post from Tim Andersson.
First he builds a new struct that satisfies the interface:
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
}
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
return &loggingResponseWriter{w, http.StatusOK}
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}
Then he's using it as a wrapper (or middleware):
func wrapHandlerWithLogging(wrappedHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
log.Printf("--> %s %s", req.Method, req.URL.Path)
lrw := NewLoggingResponseWriter(w)
wrappedHandler.ServeHTTP(lrw, req)
statusCode := lrw.statusCode
log.Printf("<-- %d %s", statusCode, http.StatusText(statusCode))
})
}
This is how it can be made with violetear, probably can give you a hint about how to deal with the status code within the handler:
package main
import (
"fmt"
"log"
"net/http"
"github.com/nbari/violetear"
)
func handleGET(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("I handle GET requests\n"))
// do anything here with the Status code
cw := w.(*violetear.ResponseWriter)
fmt.Printf("The status code is: %d\n", cw.Status())
}
func main() {
router := violetear.New()
router.HandleFunc("/", handleGET, "GET")
log.Fatal(http.ListenAndServe(":8080", router))
}
By using:
cw := w.(*violetear.ResponseWriter)
You can access the violetear.ResponseWriter which exposes the status code by using cw.Status()
You can write your own middleware, here's a very base example
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/rogierlommers/mux-status-handler/articles"
"github.com/rogierlommers/mux-status-handler/users"
)
// middleWare ...
func middleWare(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// right not all this does is log like
// "github.com/gorilla/handlers"
log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL)
// However since this is middleware you can have it do other things
// Examples, auth users, write to file, redirects, handle panics, ect
// add code to log to statds, remove log.Printf if you want
handler.ServeHTTP(w, r)
})
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/products", articles.Handler)
r.HandleFunc("/users", users.Handler)
log.Println("listening on 8080")
http.ListenAndServe(":8080", middleWare(r))
}

Routes returning 404 for mux gorilla

In my Go application, I'm using gorilla/mux.
I would like to have
http://host:3000/ to be serving files statically from the subdirectory "frontend" and
http://host:3000/api/ and its subpaths being served by the specified functions.
With the following code, neither of the calls work.
/index.html is the only one that doesn (but not the resources being loaded by it). What am I doing wrong?
package main
import (
"log"
"net/http"
"fmt"
"strconv"
"github.com/gorilla/mux"
)
func main() {
routineQuit := make(chan int)
router := mux.NewRouter().StrictSlash(true)
router.PathPrefix("/").Handler(http.FileServer(http.Dir("./frontend/")))
router.HandleFunc("/api", Index)
router.HandleFunc("/api/abc", AbcIndex)
router.HandleFunc("/api/abc/{id}", AbcShow)
http.Handle("/", router)
http.ListenAndServe(":" + strconv.Itoa(3000), router)
<- routineQuit
}
func Abc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Index!")
}
func AbcIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Todo Index!")
}
func AbcShow(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
todoId := vars["todoId"]
fmt.Fprintln(w, "Todo show:", todoId)
}
Gorilla's mux routes are evaluated in the order in which they are added. Therefore, the first route to match the request is used.
In your case, the / handler will match every incoming request, then look for the file in the frontend/ directory, then display a 404 error. You just need to swap your routes order to get it running:
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/api/abc/{id}", AbcShow)
router.HandleFunc("/api/abc", AbcIndex)
router.HandleFunc("/api", Abc)
router.PathPrefix("/").Handler(http.FileServer(http.Dir("./frontend/")))
http.Handle("/", router)

Resources