This page looks best with JavaScript enabled

Building Web Server with Go - Part 7

 ·  ☕ 5 min read
This is part 7 of 09 part series on building web server with go.
Checkout https://www.gophersumit.com/series/web-server/ for more.

Working with static files

Most of the web applications need to server static file one way or another. These files may include plain html, css, js files or assets like images, audios etc.

Go providers built in handler to work with static files.

fileserver

net/http has FileServer handler defined which returns a static file server.

1
func FileServer(root FileSystem) Handler

FileServer returns a handler that serves HTTP requests with the contents of the file system rooted at root. https://golang.org/pkg/net/http/#FileServer

Setting up a static file server is as trivial as it gets.

1
2
3
4
5
6
7
8
package main

import "net/http"

func main() {
	fileServer := http.FileServer(http.Dir("."))
	http.ListenAndServe(":3000", fileServer)
}

We are making current folder as static file server. If we curl, we should see listing on files in our project directory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ curl -i http://localhost:3000
    HTTP/1.1 200 OK
    Content-Type: text/html; charset=utf-8
    Last-Modified: Sun, 09 Feb 2020 16:09:02 GMT
    Date: Sun, 09 Feb 2020 16:11:54 GMT
    Content-Length: 127

    <pre>
    <a href="app.go">app.go</a>
    <a href="go.mod">go.mod</a>
    <a href="home.html">home.html</a>
    <a href="tmp/">tmp/</a>
    </pre>
$ curl -i http://localhost:3000/home.html
    HTTP/1.1 200 OK
    Accept-Ranges: bytes
    Content-Length: 239
    Content-Type: text/html; charset=utf-8
    Last-Modified: Sun, 09 Feb 2020 16:08:54 GMT
    Date: Sun, 09 Feb 2020 16:16:56 GMT

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Home</title>
    </head>
    <body>
        <div>
            Hello, World!
        </div>
    </body>
    </html>

We have created a static file server in few lines of code.

strip prefix

http has helper method StripPrefix() which can be used to map file system folders to predefined alternate url path.

StripPrefix returns a handler that serves HTTP requests by removing the given prefix from the request URL’s Path and invoking the handler h. StripPrefix handles a request for a path that doesn’t begin with prefix by replying with an HTTP 404 not found error. - Go Doc

1
2
3
4
5
// To serve a directory on disk (/tmp) under an alternate URL
// path (/tmpfiles/), use StripPrefix to modify the request
// URL's path before the FileServer sees it:
http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))

controlling access to files

Let’s build a simple web server which will serve all static files except .json files.
Let’s revisit the source code for FileServer

1
2
3
func FileServer(root FileSystem) Handler {
	return &fileHandler{root}
}

This FileServer function accepts an interface of FileSystem type.

1
2
3
4
type FileSystem interface {
	Open(name string) (File, error)
}

This in turn returns file interface which is defined as

1
2
3
4
5
6
7
8
type File interface {
	io.Closer
	io.Reader
	io.Seeker
	Readdir(count int) ([]os.FileInfo, error)
	Stat() (os.FileInfo, error)
}

Let’s create our own type by embedding http.File type

1
2
3
4
type CustomFile struct {
	http.File
}

We will also create our own FileSystem

1
2
3
type CustomFileSystem struct {
	http.FileSystem
}

We can update Readdir so that json files are not returned.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func (c CustomFile) Readdir(n int) (fis []os.FileInfo, err error) {
	files, err := c.File.Readdir(n)
	for _, file := range files {
		if !strings.Contains(file.Name(), ".json") {
			fmt.Println(file.Name())
			fis = append(fis, file)
		}
	}
	return
}

We also need to update Open method so that it returns error while trying to access any json file using URL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func (fs CustomFileSystem) Open(name string) (http.File, error) {
	if strings.Contains(name, ".json") {
		return nil, os.ErrPermission
	}

	file, err := fs.FileSystem.Open(name)
	if err != nil {
		return nil, err
	}
	return CustomFile{file}, err
}

Finally, while building server we need to use our custom file system

1
2
3
4
5
6
func main() {
	fs := CustomFileSystem{http.Dir(".")}
	http.Handle("/", http.FileServer(fs))
	http.ListenAndServe(":3000", nil)
}

If we try access now any json file in current directory, we should get forbidden.

1
2
3
4
5
6
7
8
9
$ curl -i  http://localhost:3000/test.json
HTTP/1.1 403 Forbidden
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Tue, 11 Feb 2020 17:41:39 GMT
Content-Length: 14

403 Forbidden

Complete code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
  "fmt"
  "net/http"
  "os"
  "strings"
)

type CustomFile struct {
  http.File
}
type CustomFileSystem struct {
  http.FileSystem
}

func (c CustomFile) Readdir(n int) (fis []os.FileInfo, err error) {
  files, err := c.File.Readdir(n)
  for _, file := range files {
      if !strings.Contains(file.Name(), ".json") {
          fmt.Println(file.Name())
          fis = append(fis, file)
      }
  }
  return
}
func (fs CustomFileSystem) Open(name string) (http.File, error) {
  if strings.Contains(name, ".json") {
      return nil, os.ErrPermission
  }
  file, err := fs.FileSystem.Open(name)
  if err != nil {
      return nil, err
  }
  return CustomFile{file}, err
}
func main() {
  fs := CustomFileSystem{http.Dir(".")}
  http.Handle("/", http.FileServer(fs))
  http.ListenAndServe(":3000", nil)
}

Serving static files, mapping to folders as well as controlling which files to show and hide is quite easy.


Sumit
WRITTEN BY
Sumit
Gopher


What's on this Page