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.
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/"))))
/files/
prefix from the path, so that the file-server doesn't attempt to find /files/go/src/...
instead./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?)/go/src/...
is readable by the user your application is running as.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.
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!