An overview of the

In Web development, you need to deal with many static resource files, such as CSS/JS and image files. This article describes how file requests are handled in the Go language. Next, we’ll look at two ways to handle file requests: the raw way and the HTTP.fileserver method.

The original way

In the original way, the file is read directly and returned to the client.

func main(a) {
  mux := http.NewServeMux()
  mux.HandleFunc("/static/", fileHandler)

  server := &http.Server {
    Addr:    ": 8080",
    Handler: mux,
  }
  iferr := server.ListenAndServe(); err ! =nil {
    log.Fatal(err)
  }
}
Copy the code

Above we created a file handler and mounted it to the path /static/. Generally, static file paths have a common prefix to distinguish them from other paths. Static /, public/, etc. The rest of the code is no different from the program template and won’t be covered here.

Note also that the registration path /static/ last/cannot be omitted. As we explained in the previous article, if the requested path is not processed with an exact match, the last part of the path is gradually removed and searched again. The request path for static files is usually /static/hello.html. There is no exact path match, then look for /static/, this path does not match /static.

Next, let’s look at the implementation of the file handler:

func fileHandler(w http.ResponseWriter, r *http.Request) {
  path := "." + r.URL.Path
  fmt.Println(path)

  f, err := os.Open(path)
  iferr ! =nil {
    Error(w, toHTTPError(err))
    return
  }
  defer f.Close()

  d, err := f.Stat()
  iferr ! =nil {
    Error(w, toHTTPError(err))
    return
  }

  if d.IsDir() {
    DirList(w, r, f)
    return
  }

  data, err := ioutil.ReadAll(f)
  iferr ! =nil {
    Error(w, toHTTPError(err))
    return
  }

  ext := filepath.Ext(path)
  ifcontentType := extensionToContentType[ext]; contentType ! ="" {
    w.Header().Set("Content-Type", contentType)
  }

  w.Header().Set("Content-Length", strconv.FormatInt(d.Size(), 10))
  w.Write(data)
}
Copy the code

First we read out the request path, plus the relative executable path. Generally, the static directory is in the same directory as the executable. Then open the path to view the information. If the path represents a file, set content-Type based on the file’s suffix to read the contents of the file and return. The code simply lists several suffixes corresponding to the content-Type:

var extensionToContentType = map[string]string {
  ".html": "text/html; charset=utf-8".".css": "text/css; charset=utf-8".".js": "application/javascript".".xml": "text/xml; charset=utf-8".".jpg":  "image/jpeg",}Copy the code

If the path represents a directory, return a list of all files and directories in the directory:

func DirList(w http.ResponseWriter, r *http.Request, f http.File) {
  dirs, err := f.Readdir(- 1)
  iferr ! =nil {
    Error(w, http.StatusInternalServerError)
    return
  }
  sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })

  w.Header().Set("Content-Type"."text/html; charset=utf-8")
  fmt.Fprintf(w, "<pre>\n")
  for _, d := range dirs {
    name := d.Name()
    if d.IsDir() {
      name += "/"
    }
    url := url.URL{Path: name}
    fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), name)
  }
  fmt.Fprintf(w, "</pre>\n")}Copy the code

The above function reads the files and directories at the bottom of the directory and then sorts them by name. It is finally assembled into HTML that contains hyperlinks. Users can click hyperlinks to access corresponding files or directories.

If an Error occurs in the above process, we use the toHTTPError function to convert the Error into the corresponding response code, and then send the Error back to the client.

func toHTTPError(err error) int {
  if os.IsNotExist(err) {
    return http.StatusNotFound
  }
  if os.IsPermission(err) {
    return http.StatusForbidden
  }
  return http.StatusInternalServerError
}

func Error(w http.ResponseWriter, code int) {
  w.WriteHeader(code)
}
Copy the code

Static directory contents of the same directory:

The static ├ ─ ─ folder │ ├ ─ ─ file1. TXT │ └ ─ ─ file2. TXT │ └ ─ ─ file3. TXT ├ ─ ─ hello. CSS ├ ─ ─ hello. HTML ├ ─ ─ hello. Js └ ─ ─ hello.txtCopy the code

Run the program to see the effect:

$ go run main.go
Copy the code

Open a browser, request localhost: 8080 / static/hello. HTML:

You can see that the page hello.html is rendered:

<! -- hello.html -->

      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Static files for Go Web programming</title>
  <link rel="stylesheet" href="/static/hello.css">
</head>
<body>
  <p class="greeting">Hello World!</p>
  <script src="/static/hello.js"></script>
</body>
</html>
Copy the code

The CSS and JS files used by HTML are also requested via /static/ path, and both files are relatively simple:

.greeting {
  font-family: sans-serif;
  font-size: 15px;
  font-style: italic;
  font-weight: bold;
}
Copy the code
console.log("Hello World!")
Copy the code

“Hello World!” Fonts are displayed as CSS styles, and js printed information can also be seen by looking at the console.

Localhost :8080/static/ :

You can click on the files in the list to see their contents.

Click on the hello. CSS:

Click on the hello. Js:

Click On Folder and file1.txt in sequence:

The static file request path is also printed to the running server’s console:

$ go run main.go 
./static/
./static/hello.css
./static/hello.js
./static/folder/
./static/folder/file1.txt

Copy the code

One drawback of the original approach is that the implementation logic is complex. The code above is not small, even though we have neglected to handle many cases. Writing your own is tedious and bug-prone. The logic of the static file service is fairly consistent and should be provided in the form of a library. For this purpose, the Go language provides HTTP.FileServer methods.

http.FileServer

Here’s how to use it:

package main

import (
  "log"
  "net/http"
)

func main(a) {
  mux := http.NewServeMux()
  mux.Handle("/static/", http.FileServer(http.Dir("")))


  server := &http.Server {
    Addr: ": 8080",
    Handler: mux,
  }

  iferr := server.ListenAndServe(); err ! =nil {
    log.Fatal(err)
  }
}
Copy the code

The above code uses the http.Server method. A few lines of code achieve the same effect as the original method. That’s the benefit of using libraries!

FileServer accepts a variable of HTTP.FileSystem interface type:

// src/net/http/fs.go
type FileSystem interface {
  Open(name string) (File, error)
}
Copy the code

Dir is a type. Note that http.Dir is a string, not a method. So http.dir (“”) is just a type conversion, not a method call:

// src/net/http/fs.go
type Dir string
Copy the code

Http. Dir indicates the start path of the file. If the file is empty, it indicates the current path. When the Open method is called, the argument passed in needs to concatenate the starting path in front of the actual file path.

The return value of http.FileServer is HTTP.Handler, so you need to register the Handler with the Handle method. HTTP.FileServer sends the received request path to http.Dir’s Open method to Open the corresponding file or directory for processing. In the above program, if the request path is /static/hello.html, the initial path of http.dir is concatenated, and the file with the path of./static/hello.html is read.

Sometimes we want the processor’s registration path to be different from the http.dir start path. Some tools output static files to the public directory when they are packaged. You need to use the http.stripprefix method, which strips the specific prefix from the request path before processing:

package main

import (
  "log"
  "net/http"
)

func main(a) {
  mux := http.NewServeMux()
  mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("./public"))))


  server := &http.Server {
    Addr: ": 8080",
    Handler: mux,
  }

  iferr := server.ListenAndServe(); err ! =nil {
    log.Fatal(err)
  }
}
Copy the code

At this time, the request localhost: 8080 / static/hello. HTML will return. / public/hello. HTML file. The /static/index.html path passes through the handler http.stripprefix to get /index.html, /public to get the final path to the file./public/hello.

HTTP.FileServer can also infer the content type based on the suffix of the requested file.

// src/mime/type.go
var builtinTypesLower = map[string]string{
  ".css":  "text/css; charset=utf-8".".gif":  "image/gif".".htm":  "text/html; charset=utf-8".".html": "text/html; charset=utf-8".".jpeg": "image/jpeg".".jpg":  "image/jpeg".".js":   "application/javascript".".mjs":  "application/javascript".".pdf":  "application/pdf".".png":  "image/png".".svg":  "image/svg+xml".".wasm": "application/wasm".".webp": "image/webp".".xml":  "text/xml; charset=utf-8",}Copy the code

If the file suffix cannot be inferred, http.fileserver reads the first 512 bytes of the file to infer the content type based on the content. SRC /net/ HTTP /sniff. Go

http.ServeContent

In addition to using HTTP.fileserver directly, the NET/HTTP library also exposes the ServeContent method. This method is easy to use when the processor needs to return the contents of a file.

For example, the following program returns the contents of the corresponding file according to the file parameter in the URL:

package main

import (
  "fmt"
  "log"
  "net/http"
  "os"
  "time"
)

func ServeFileContent(w http.ResponseWriter, r *http.Request, name string, modTime time.Time) {
  f, err := os.Open(name)
  iferr ! =nil {
    w.WriteHeader(500)
    fmt.Fprint(w, "open file error:", err)
    return
  }
  defer f.Close()

  fi, err := f.Stat()
  iferr ! =nil {
    w.WriteHeader(500)
    fmt.Fprint(w, "call stat error:", err)
    return
  }

  if fi.IsDir() {
    w.WriteHeader(400)
    fmt.Fprint(w, "no such file:", name)
    return
  }

  http.ServeContent(w, r, name, fi.ModTime(), f)
}

func fileHandler(w http.ResponseWriter, r *http.Request) {
  query := r.URL.Query()
  filename := query.Get("file")

  if filename == "" {
    w.WriteHeader(400)
    fmt.Fprint(w, "filename is empty")
    return
  }

  ServeFileContent(w, r, filename, time.Time{})
}

func main(a) {
  mux := http.NewServeMux()
  mux.HandleFunc("/show", fileHandler)

  server := &http.Server {
    Addr:    ": 8080",
    Handler: mux,
  }

  iferr := server.ListenAndServe(); err ! =nil {
    log.Fatal(err)
  }
}
Copy the code

In addition to accepting HTTP.ResponseWriter and HTTP.Request, HTTP.

The modTime parameter is used to set the last-Modified header of the response. If the request has an if-Modified-since header, the ServeContent method determines whether to send the content based on modTime. If you need to send content, the ServeContent method re-reads the content from the IO.ReadSeeker interface. * os.file implements the interface IO.ReadSeeker.

Usage scenarios

Static resources in Web development can be handled using HTTP.fileserver. In addition, HTTP.FileServer can also be used to implement a simple FileServer to browse or download files:

package main

import (
  "flag"
  "log"
  "net/http"
)

var (
  ServeDir string
)

func init(a) {
  flag.StringVar(&ServeDir, "sd".". /"."the directory to serve")}func main(a) {
  flag.Parse()

  mux := http.NewServeMux()
  mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(ServeDir))))


  server := &http.Server {
    Addr:    ": 8080",
    Handler: mux,
  }

  iferr := server.ListenAndServe(); err ! =nil {
    log.Fatal(err)
  }
}
Copy the code

In the above code, we built a simple file server. After compiling, you can browse and download files in the directory you want to browse by passing it as an argument to the command line option:

$ ./main.exe -sd D:/code/golang
Copy the code

You can also use the port as a command-line option to create a general-purpose file server that can be compiled to use 😀 on other machines.

conclusion

This article describes how to handle static files, including the original method, http.fileserver, and http.servecontent. Finally, using HTTP.FileServer to achieve a simple FileServer, for daily use.

reference

  1. Go Web programming
  2. Net/HTTP document

I

My blog

Welcome to follow my wechat public account [GoUpUp], learn together, progress together ~

This article is published by OpenWrite!