http.FileServer handling http codes correctly?

Hello,

I was playing with the file server and wanted to route root search to a index.html located inside a http.FileServer.

Im having the following code to demonstrate.

import (
        "fmt"
        "io/ioutil"
        "net/http"
        "net/http/httputil"
        "os"
        "strings"
)

var staticHttp = http.NewServeMux()

func ApplicationHandler(w http.ResponseWriter, r *http.Request) {
        dump, err := httputil.DumpRequest(r, true)
        if err != nil {
                http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
                return
        }
        fmt.Printf("%q \n", dump)

        if strings.Contains(r.URL.Path, ".") {
                staticHttp.ServeHTTP(w, r)
        } else {
                http.Redirect(w, r, "/index.html", http.StatusPermanentRedirect)
        }

}
func main() {
        defer os.Remove("./dist/index.html")
        defer os.Remove("./dist/another.file")
        defer os.Remove("./dir")
        _ = os.Mkdir("./dist", 0777)
        _ = ioutil.WriteFile("./dist/index.html", []byte("MY APP"), 0777)
        _ = ioutil.WriteFile("./dist/another.file", []byte("another file"), 0777)

        http.HandleFunc("/", ApplicationHandler)
        staticHttp.Handle("/", http.FileServer(http.Dir("./dist")))
        http.ListenAndServe(":9999", nil)
}

This code dont work when i visit ‘/’ my browser will start loop.

Solution for my problem is just to use http.Handle("/", http.FileServer(http.Dir("./dist"))) since by default the browser seems to look for index.html

I share on the forum to try find out what causes the behaviour when I route a StatusPermanentRedirect to a http.FileServer.

Basically you have two pages and you must have two distinct handlers for this. eg:

http.HandleFunc("/a", aHandler)
http.HandleFunc("/b", bHandler)

You can also use gorrila mux for routes. Maybe works if you access a page from a file server but i think is a very bad practice :open_mouth:

Note that we have two mux,

I’m just passing a request between two mux handlers and I think that is alright to do. Keep in mind they are not sharing mux cuz it causes panic if routes are shared between handlers.

I’m guessing it’s correct practice, imagine if you have one api part and one web application part, in theory they can be separated.

This is what a servemux is. It’s responsibility is rouring?

ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

I did more digging,

It seems that the code works as expected if i redirect another resource like: /another.file
but if i redirect to index.html then i get problem.

Using http.Redirect(w, r, "/another.file", http.StatusPermanentRedirect)
######	PROCESSING NEW REQUEST	######
GET / HTTP/1.1
Host: localhost:9999
Accept: */*
User-Agent: curl/7.51.0

REDIRECTING /

 
######	PROCESSING NEW REQUEST	######
GET /another.file HTTP/1.1
Host: localhost:9999
Accept: */*
User-Agent: curl/7.51.0

REDIRECTING /another.file TO STATIC FILE MUX

Using: http.Redirect(w, r, “/index.html”, http.StatusPermanentRedirect)

######	PROCESSING NEW REQUEST	######
GET / HTTP/1.1
Host: localhost:9999
Accept: */*
User-Agent: curl/7.51.0

REDIRECTING /

 
######	PROCESSING NEW REQUEST	######
GET /index.html HTTP/1.1
Host: localhost:9999
Accept: */*
User-Agent: curl/7.51.0

REDIRECTING /index.html TO STATIC FILE MUX

 
######	PROCESSING NEW REQUEST	######
GET / HTTP/1.1
Host: localhost:9999
Accept: */*
User-Agent: curl/7.51.0

REDIRECTING /

 
######	PROCESSING NEW REQUEST	######
GET /index.html HTTP/1.1
Host: localhost:9999
Accept: */*
User-Agent: curl/7.51.0

REDIRECTING /index.html TO STATIC FILE MUX

 
######	PROCESSING NEW REQUEST	######
GET / HTTP/1.1
Host: localhost:9999
Accept: */*
User-Agent: curl/7.51.0

REDIRECTING /

Updated debugging code:
import (
“fmt”
“io/ioutil”
“net/http”
“net/http/httputil”
“os”
“strings”
)

var staticHttp = http.NewServeMux()

func ApplicationHandler(w http.ResponseWriter, r *http.Request) {

	fmt.Println("######\tPROCESSING NEW REQUEST\t######")
	dump, err := httputil.DumpRequest(r, false)
	if err != nil {
		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
		return
	}

	fmt.Printf("%s", dump)
	if strings.Contains(r.URL.Path, ".") {
		fmt.Printf("REDIRECTING %s TO STATIC FILE MUX\n", r.URL)
		staticHttp.ServeHTTP(w, r)

	} else {
		fmt.Printf("REDIRECTING %s\n", r.URL)
		//http.Redirect(w, r, "/another.file", http.StatusPermanentRedirect)
		http.Redirect(w, r, "/index.html", http.StatusPermanentRedirect)
	}
	fmt.Printf("\n \n")

}
func main() {
	defer os.Remove("./dist/index.html")
	defer os.Remove("./dist/another.file")
	defer os.Remove("./dir")
	_ = os.Mkdir("./dist", 0777)
	_ = ioutil.WriteFile("./dist/index.html", []byte("MY APP"), 0777)
	_ = ioutil.WriteFile("./dist/another.file", []byte("another file"), 0777)

	http.HandleFunc("/", ApplicationHandler)
	staticHttp.Handle("/", http.FileServer(http.Dir("./dist")))
	http.ListenAndServe(":9999", nil)
}

See the docs for FileServer:

As a special case, the returned file server redirects any request ending in “/index.html” to the same path, without the final “index.html”.

You may have to serve the files yourself using the other functions in that package if you want to avoid this (or just copy out FileServer and change the bit that does this, and use your own copy).

2 Likes