DEV Community

Rinat Almakhov
Rinat Almakhov

Posted on

Why you should not use FileServer in order to serve react application.

While developing web server for reactjs I encoutered some unexpected issues and for a while I have been considering that I should not start using net/http at all.

There are tons of articles about “how to develop golang web application that will serve static files with net/http module“. Below I am going to explain why you should not do this.


In order to serve static files you had better consider to use following:

  • nginx
  • Aws CloudFront / s3
  • other server/cloud service

Additional functionality.

It seems that net/http has all that you want. It has Fileserver, ... and so on. It provides additional features as like: content size, defining mime-types. But unfortunately you can’t disable it. E.g. if match can blow your mind. Browser expects content, but your server will respond 304 instead and you get blank page.


func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
    if r.Method != "GET" && r.Method != "HEAD" {
        return condNone
    ims := r.Header.Get("If-Modified-Since")
    if ims == "" || isZeroTime(modtime) {
        return condNone
    t, err := ParseTime(ims)
    if err != nil {
        return condNone
    // The Last-Modified header truncates sub-second precision so
    // the modtime needs to be truncated too.
    modtime = modtime.Truncate(time.Second)
    if modtime.Before(t) || modtime.Equal(t) {
        return condFalse
    return condTrue
Enter fullscreen mode Exit fullscreen mode

above function checks "If-Modified-Since" header, then responds accordingly. However this code causes issue when your browser tries to load react application that was loaded earlier. You will see blank page, and you would have to reload page.

Primer grabbed from

Serve is a very simple static file server in go
    -p="8100": port to serve on
    -d=".":    the directory of static files to host
Navigating to http://localhost:8100 will display the index.html or directory
listing file.
package main

import (

func main() {
    port := flag.String("p", "8100", "port to serve on")
    directory := flag.String("d", ".", "the directory of static file to host")

    http.Handle("/", http.FileServer(http.Dir(*directory)))

    log.Printf("Serving %s on HTTP port: %s\n", *directory, *port)
    log.Fatal(http.ListenAndServe(":"+*port, nil))
Enter fullscreen mode Exit fullscreen mode

There is an issue in above code: If-Modified-Since issue.

How have i fixed this issue in my project

delete If-Modified-Since header:

// ...

if r.Header.Get(ifModifiedSinceHeader) != "" && r.Method == http.MethodGet {

// ...
Enter fullscreen mode Exit fullscreen mode

ResponseWriter doesn’t cover all needs

Have you tried to catch status code with net/http package?

It is stupid but it is really complicated thing.

But why it can be needed?

  • you are going to have logging (just simple access logs)
  • you want to handle status code in middleware

Obviously responseWriter is intended only to write. Hence you need to use a proxy writer, e.g.:

// original file is

type ResponseWriter interface {

    // Returns the HTTP response status code of the current request.
    Status() int

    // Returns the number of bytes already written into the response http body.
    // See Written()
    Size() int

    // Writes the string into the response body.
    WriteString(string) (int, error)

    // Returns true if the response body was already written.
    Written() bool

    // Forces to write the http header (status code + headers).

    // get the http.Pusher for server push
    Pusher() http.Pusher

type responseWriter struct {
    size   int
    status int


func (w *responseWriter) Status() int {
    return w.status

func (w *responseWriter) Size() int {
    return w.size
Enter fullscreen mode Exit fullscreen mode

This code allows you to get status code and size when you need it.

However even though you can implement such a responseWriter it will return http response once your code writes either status or data. It means you are not able to substitute 404 or 403 errors.

Slow HTTP request vulnerability

Let’s see Server struct:

type Server struct {
    // ...

    ReadTimeout time.Duration
    WriteTimeout time.Duration

Enter fullscreen mode Exit fullscreen mode

By default ReadTimeout and WriteTimeout have zero value. It means there will be no timeout.

So your application will have slow HTTP vulnerability.

Slow HTTP attacks are denial-of-service (DoS) attacks in which the attacker sends HTTP requests in pieces slowly, one at a time to a Web server. If an HTTP request is not complete, or
if the transfer rate is very low, the server keeps its resources busy
waiting for the rest of the data.

What i’ve done:

func newServer(addr string, handler http.Handler) *http.Server {
    srv := &http.Server{
        ReadTimeout:  120 * time.Second,
        WriteTimeout: 120 * time.Second,
        IdleTimeout:  120 * time.Second,
        Handler:      handler,
        Addr:         addr,
    return srv
Enter fullscreen mode Exit fullscreen mode

Mime types

Another small issue is lack of mime types. By default FileServer doesn’t give a proper mime type for files. It returns always a text type.

While building of docker image i add mime.types file


COPY mime.types /etc/mime.types

# ..
Enter fullscreen mode Exit fullscreen mode

Despite the above, i used standart library for my own project.

Why i started developing SCWS: static content web server

Have you ever tried to publish REACT application?

You might be familiar how to set up nginx in order to serve react app. Let’s see.


server {
  listen 8080;
# Always serve index.html for any request
  location / {
    # Set path
    root /var/www/;
    try_files $uri /index.html;
Enter fullscreen mode Exit fullscreen mode


FROM node:16-stretch AS demo
RUN git clone
RUN cd test-client && npm install && npm run build

FROM nginx:1.16.1
COPY --from=demo /code/test-client/build/ /var/www/
ADD site.conf /etc/nginx/conf.d/site.conf
Enter fullscreen mode Exit fullscreen mode

Then you can run it within docker:

docker build -t react-local:test .
docker run -p 8080:8080 react-local:test
Enter fullscreen mode Exit fullscreen mode

Also for my production needs i need to have some features:

  • prometheus metrics
  • jaeger tracing
  • health check

Nginx doesn’t have these features out of the box. So i have to install:

SCWS has such features and more:

  • prometheus metrics
  • jaeger tracing
  • health check
  • settings for react app

I only want to describe the last feature.

For example, there are 2 environments: production and testing.

On production I have to show title “Production”, on testing - “Testing”.

In order to achive this i can use env variables from process.env.

But i would have to build image for 2 envs. So I would not able to use 1 docker image for testing and production.

How i solved this issue with settings feature

SCWS has built-in url: /_/settings. The url responds json containing env variables, e.g.:


FROM node:16-stretch AS demo
RUN git clone
RUN cd test-client && npm install && npm run build

COPY --from=demo /code/test-client/build/ /www/
Enter fullscreen mode Exit fullscreen mode


docker build -t example:test .
docker run -e SCWS_SETTINGS_VAR_TITLE="Production" -p 8080:8080 example:test
# get json
Enter fullscreen mode Exit fullscreen mode


Enter fullscreen mode Exit fullscreen mode

This feature allows to expose env vars with prefix SCWS_SETTINGS_VAR_ .

Your react app has to send GET request to url: /_/settings and then it will get json data.

If you find it interesting and useful, please get familiar with SCWS github repo

Thanks for reading it.


Top comments (0)