Basic Authentication for static resources - http

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.

Related

How to server static files with virtual hosts functioality in Go

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

Golang getting a 404 whilst server is running

I am trying to run a basic web app, following a tutorial, using Golang and the routing package Gorilla/mux. The server is running fine but it refuses to find the index.html file regardless of what I put in the browser, always returning a 404.
Here is the code:
main.go
package main
import (
"database/sql"
"fmt"
"net/http"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
)
const (
host = "localhost"
port = 5432
user = "postgres"
password = "0102"
dbname = "bird_encyclopaedia"
)
func newRouter() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/hello", handler).Methods("GET")
staticFileDirectory := http.Dir("./assets/")
staticFileHandler := http.StripPrefix("/assets/", http.FileServer(staticFileDirectory))
r.PathPrefix("/assets/").Handler(staticFileHandler).Methods("GET")
r.HandleFunc("/bird", getBirdHandler).Methods("GET")
r.HandleFunc("/bird", createBirdHandler).Methods("POST")
return r
}
func main() {
fmt.Println("Starting server dickface...")
connString := fmt.Sprintf("host=%s port=%d user=%s "+
"password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
db, err := sql.Open("postgres", connString)
if err != nil {
panic(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
InitStore(&dbStore{db: db})
r := newRouter()
fmt.Println("Serving on port 8080")
http.ListenAndServe(":8080", r)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
The html file is just in the assets/index.html directory, I can supply that if need be but I can't see the problem being in the actual html?
I have trawled through the code many times and cannot see why the server would not be able to find the directory. I have tried localhost/8080/assets, localhost/8080/assets/index.html, localhost/8080, and all other variants.
If I append it with /hello mind it returns the Hello world as seen in main.go
And if I append it with /bird it returns "null" instead of 404.
You don't need http.StripPrefix() since you are not using the assets in the URL.
Just change these two lines:
staticFileHandler := http.StripPrefix("/assets/", http.FileServer(staticFileDirectory))
r.PathPrefix("/assets/").Handler(staticFileHandler).Methods("GET")
to
staticFileHandler := http.FileServer(staticFileDirectory)
r.PathPrefix("/").Handler(staticFileHandler).Methods("GET")

HTTP2 not enabled by default on localhost

I might be just clueless on this but my basic localhost server doesn't have HTTP2 Enabled for some odd reason, I normally proxy behind Caddy, but as I don't want to use my domain for this side project, I created a basic server in Go, and ran it, it works okay, but the headers show HTTP/1.1 instead of 2.0, what's wrong?
package main
import (
"fmt"
"net/http"
"html/template"
"os"
)
func IfError(err error, Quit bool) {
if err != nil {
fmt.Println(err.Error())
if(Quit) {
os.Exit(1);
}
}
}
func ServeHome(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("html/home")
IfError(err, false)
err = t.Execute(w, nil)
IfError(err, false)
}
func RedirectRoot(fs http.Handler, home http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
home.ServeHTTP(w, r)
} else {
fs.ServeHTTP(w, r)
}
})
}
func main() {
proto := ":8081"
ServeFiles := http.FileServer(http.Dir("static/"))
http.Handle("/", RedirectRoot(ServeFiles, http.HandlerFunc(ServeHome)))
fmt.Printf("Listening on ... %s", proto)
IfError(http.ListenAndServe(proto, nil), true)
}
Very basic stuff, but doesn't work even thought the documentation says it works by default. Also, my go version is 1.8.3
Yes, its enabled by default when you use with SSL certs.
Doc Reference: Starting with Go 1.6, the http package has transparent
support for the HTTP/2 protocol when using HTTPS.
err := http.ListenAndServeTLS(":8081", "server.crt", "server.key", handler)
if err != nil && err != http.ErrServerClosed {
log.Fatal("ListenAndServe: ", err)
}
Then, access it via
https://localhost:8081/

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