1

I have a file in the folder files located in the root of my Golang project directory called test.jpg. So that would be ./files/test.jpg that I want to serve to my front end and I am having difficulties.

My golang project is in a docker file with the following volume attached. (Basically this says that /go/.../webserver in the docker container can read and write out of ./ on the server)

volumes:
  - ./:/go/src/github.com/patientplatypus/webserver/

I am using gorilla/mux for routing as defined by:

r := mux.NewRouter()

Here are some of my attempts at trying to get the PathPrefix to be properly formatted:

r.PathPrefix("/files/").Handler(http.StripPrefix("/files/", 
http.FileServer(http.Dir("/go/src/github.com/patientplatypus/webserver/files/"))))

or

r.PathPrefix("/files").Handler(http.FileServer(http.Dir("./files/")))

or

r.PathPrefix("/files/").Handler(http.StripPrefix("/files/",  
http.FileServer(http.Dir("./"))))

or

r.PathPrefix("/files/").Handler(http.FileServer(
http.Dir("/go/src/github.com/patientplatypus/webserver/")))

I had previously successfully written to the server with the following command:

newPath := filepath.Join("/go/src/github.com/patientplatypus/webserver/files", 
"test.jpg")
newFile, err := os.Create(newPath)

So my intuition is that the first of my attempts should be the correct one, specfying the entirety of the /go/.../files/ path.

In any case, each of my attempts successfully returns a 200OK to my front end with an empty response.data as shown here:

Object { data: "", status: 200, statusText: "OK",
headers: {…}, config: {…}, request: XMLHttpRequest }

which is coming from a simple js frontside http request using the axios package:

        axios({
            method: 'get',
            url: 'http://localhost:8000/files/test.jpg',
        })
        .then(response => {
            //handle success
            console.log("inside return for test.jpg and value 
            of response: ")
            console.log(response);
            console.log("value of response.data: ")
            console.log(response.data)
            this.image = response.data;
        })
        .catch(function (error) {
            //handle error
            console.log(error);
        });

My only guess as to why this is happening is that it doesn't see the file and so returns nothing.

For such a seemingly trivial problem, I am in the guess and check stage and I don't know how to debug further. If anyone has any ideas please let me know.

Thanks!

EDIT:

In order to be completely clear, here is the entirety of my main.go file (only 150 lines) with all the routing. There is a piece of middleware that handles JWT auth, but in this case it ignores this route, and there is middleware to handle CORS.

Line in question is highlighted: https://gist.github.com/patientplatypus/36230e665051465cd51291e17825f1f5#file-main-go-L122.

Also, here is my docker-compose file that shows what the volumes are (please ignore the sleep command to boot postgres - I understand it's not the ideal implementation)

https://gist.github.com/patientplatypus/2569550f667c0bee51287149d1c49568.

There should be no other information necessary to debug.

Also...some people have wanted me to show that the file exists in the container.

patientplatypus:~/Documents/zennify.me:11:49:09$docker exec 
backend_app_1 ls /go/src/github.com/patientplatypus/webserver/files
test.jpg

Shows that it does.

CLARIFICATION:

I was not properly using Axios to buffer the array, and get an image. An example of that working is here:

        //working example image
        var imageurl = 'https://avatars2.githubusercontent.com/u/5302751?v=3&s=88';
        //my empty server response
        //var imageurl = 'http://localhost:8000/files/test.jpg';
        axios.get(imageurl, {
            responseType: 'arraybuffer'
        })
        .then( response => {
            console.log('value of response: ', response);
            console.log('value of response.data: ', response.data);
            const base64 = btoa(
                new Uint8Array(response.data).reduce(
                    (data, byte) => data + String.fromCharCode(byte),
                    '',
                ),
            );
            console.log('value of base64: ', base64);
            this.image = "data:;base64," + base64 
        });

However, this does not change the underlying problem that it appears that golang is not returning any data (response.data is empty). Just FYI that I have confirmed a working axios and that doesn't fix the issue.

Peter Weyand
  • 2,159
  • 9
  • 40
  • 72

4 Answers4

1

Can you eliminate the JavaScript client from this, and make a simple curl request? Replace the image with a simple text file to remove any possible Content-Type / MIME detection issues.

(Slightly) adapting the example posted in the gorilla/mux docs: https://github.com/gorilla/mux#static-files

Code

func main() {
    var dir string

    flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
    flag.Parse()
    r := mux.NewRouter()

    r.PathPrefix("/files/").Handler(
        http.StripPrefix("/files/",
            http.FileServer(
                http.Dir(dir),
            ),
        ),
    )

    addr := "127.0.0.1:8000"
    srv := &http.Server{
        Handler:      r,
        Addr:         addr,
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

    log.Printf("listening on %s", addr)
    log.Fatal(srv.ListenAndServe())
}

Running the Server

➜  /mnt/c/Users/matt/Dropbox  go run static.go -dir="/home/matt/go/src/github.com/gorilla/mux"
2018/09/05 12:31:28 listening on 127.0.0.1:8000

Getting a file

➜  ~  curl -sv localhost:8000/files/mux.go | head
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /files/mux.go HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Length: 17473
< Content-Type: text/plain; charset=utf-8
< Last-Modified: Mon, 03 Sep 2018 14:33:19 GMT
< Date: Wed, 05 Sep 2018 19:34:13 GMT
<
{ [16384 bytes data]
* Connection #0 to host localhost left intact
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package mux

import (
        "errors"
        "fmt"
        "net/http"

Note that the "correct" way to achieve this is as per your first example:

r.PathPrefix("/files/").Handler(http.StripPrefix("/files/", 
http.FileServer(http.Dir("/go/src/github.com/patientplatypus/webserver/files/"))))
  • Strip the /files/ prefix from the path, so that the file-server doesn't attempt to find /files/go/src/... instead.
  • Ensure that /go/src/... is correct - you are providing an absolute path from the root of your filesystem, NOT from your home directory (is this your intent?)
  • If that is your intent, make sure that /go/src/... is readable by the user your application is running as.
elithrar
  • 23,364
  • 10
  • 85
  • 104
  • So dir doesn't work ~ `r.PathPrefix("/files/").Handler(http.StripPrefix("/files/", http.FileServer(http.Dir(dir))))` results in `./main.go:123: undefined: dir`. As far as I can tell dir is not a reserved word. I'll look into making a call through postman and see what happens. EDIT: oic - dir simply === '.'. Hmmm..... – Peter Weyand Sep 05 '18 at 19:55
  • `dir` is a flag, so don't elide part of my example code. It defaults to the current directory - `.` - but is overridden when you pass the flag, as I did in my example. – elithrar Sep 05 '18 at 20:00
  • ooooohhhhhhhhhhh that makes sense. – Peter Weyand Sep 05 '18 at 20:02
  • So, if I use `r.PathPrefix("/files/").Handler(http.StripPrefix("/files/", http.FileServer(http.Dir("/go/src/github.com/patientplatypus/webserver/files/"))))` and postman `http://localhost:8000/files/blah.txt` where `blah.txt` exists, I get `200OK` with no body. If I do the same with `blah2.txt` where `blah2.txt` *doesn't exist* I get the same. Something seems very wrong. (`blah.txt` has some `helloworld` in it btw). – Peter Weyand Sep 05 '18 at 20:08
  • My suggestion: clean up your post. Remove the JavaScript part. Focus on the Go piece, and lay out a minimal example that someone can copy paste and run. You haven't done that yet, so there's something else in the mix here - another route, perhaps? – elithrar Sep 05 '18 at 20:17
  • And the results of a curl request on both `blah.txt` and `blah2.txt` confirm that the content length in both cases is 0, as well as `test.jpg`. All of them also appear to be trying to return a content type of text which is very strange. https://gist.github.com/patientplatypus/54e4077dd251afb5a587bb38063bfaaf. – Peter Weyand Sep 05 '18 at 20:18
0

I usually just bundle file assets into my compiled application. Then the application will run and retrieve assets the same way whether you run in a container or locally. Here's a link to one approach. I've used go-bindata in the past but it appears as if this package is no longer maintained but there are many alternatives available.

0

So, this is not a great solution, but it works (for the moment). I saw this post: Golang. What to use? http.ServeFile(..) or http.FileServer(..)?, and apparently you can use the lower level api servefile rather than fileserver which has added protections.

So I can use

r.HandleFunc("/files/{filename}", util.ServeFiles)

and

func ServeFiles(w http.ResponseWriter, req *http.Request){
    fmt.Println("inside ServeFiles")
    vars := mux.Vars(req)
    fileloc := "/go/src/github.com/patientplatypus/webserver/files"+"/"+vars["filename"]
    http.ServeFile(w, req, fileloc)
}

Again not a great solution and I'm not thrilled - I'll have to pass along some auth stuff in the get request params in order to prevent 1337h4x0rZ. If anyone knows how to get pathprefix up and running please let me know and I can refactor. Thank you everyone who helped!

Peter Weyand
  • 2,159
  • 9
  • 40
  • 72
  • You should do [what @elithrar suggested](https://stackoverflow.com/questions/52191541/gorilla-golang-pathprefix-not-serving-files#comment91335365_52192329). – Michael Hampton Sep 06 '18 at 02:14
0

I am on the exact same page with you, totally same, and I've been investigating this thing to make it work for the whole day. I curled, and it also gave me back 200 with 0 bytes as you mentioned in the comments above. I was blaming docker.

And it worked finally, and the only (...) change is I removed the trailing slash inside the http.Dir()

E.g.: In your example, make this: http.FileServer(http.Dir("/go/src/github.com/patientplatypus/webserver/files")))), don't add the last slash to make it .../files/

And it Worked. It showed up the picture, and the curl result as well:

HTTP/2 200 
 accept-ranges: bytes
 content-type: image/png
 last-modified: Tue, 15 Oct 2019 22:27:48 GMT
 content-length: 107095
Eduardo Baitello
  • 10,469
  • 7
  • 46
  • 74
Sine
  • 1