Skip to content
This repository was archived by the owner on May 29, 2018. It is now read-only.

Commit 33498b9

Browse files
committed
handler for uploading and downloading
1 parent 79d957e commit 33498b9

File tree

5 files changed

+272
-0
lines changed

5 files changed

+272
-0
lines changed

download_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package httpfstream
2+
3+
import (
4+
"net/http"
5+
"os"
6+
"strings"
7+
"testing"
8+
)
9+
10+
type downloadTest struct {
11+
path string
12+
body string
13+
14+
writeFiles map[string]string
15+
16+
// error responses
17+
statusCode int
18+
msg string
19+
}
20+
21+
func TestDownload(t *testing.T) {
22+
tests := []downloadTest{
23+
{path: "/foo", body: "bar", writeFiles: map[string]string{"/foo": "bar"}, statusCode: http.StatusOK},
24+
25+
{path: "/doesntexist", statusCode: http.StatusNotFound, msg: "404 page not found"},
26+
}
27+
for _, test := range tests {
28+
testDownload(t, test)
29+
}
30+
}
31+
32+
func testDownload(t *testing.T, test downloadTest) {
33+
label := test.path
34+
35+
server := newTestServer()
36+
defer server.close()
37+
38+
for path, data := range test.writeFiles {
39+
w, err := server.fs.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL)
40+
if err != nil {
41+
t.Fatalf("%s: fs.WriterOpen: %s", label, err)
42+
}
43+
_, err = w.Write([]byte(data))
44+
if err != nil {
45+
t.Fatalf("%s: Write: %s", label, err)
46+
}
47+
err = w.Close()
48+
if err != nil {
49+
t.Fatalf("%s: Close: %s", label, err)
50+
}
51+
}
52+
53+
req, err := http.NewRequest("GET", server.URL+test.path, nil)
54+
if err != nil {
55+
t.Fatalf("%s: NewRequest: %s", label, err)
56+
}
57+
58+
resp, err := http.DefaultClient.Do(req)
59+
if err != nil {
60+
t.Fatalf("%s: Do: %s", label, err)
61+
}
62+
63+
if test.statusCode != resp.StatusCode {
64+
t.Errorf("%s: want StatusCode == %d, got %d", label, test.statusCode, resp.StatusCode)
65+
}
66+
67+
body := string(readAll(t, resp.Body))
68+
if test.statusCode >= 200 && test.statusCode <= 299 {
69+
if test.body != body {
70+
t.Errorf("%s: want data == %q, got %q", label, test.body, body)
71+
}
72+
} else {
73+
msg := strings.TrimSpace(body)
74+
if test.msg != msg {
75+
t.Errorf("%s: want error message %q, got %q", label, test.msg, msg)
76+
}
77+
}
78+
}

http_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package httpfstream
2+
3+
import (
4+
"github.com/sourcegraph/rwvfs"
5+
"io"
6+
"io/ioutil"
7+
"net/http"
8+
"net/http/httptest"
9+
"os"
10+
"testing"
11+
)
12+
13+
type testServer struct {
14+
*httptest.Server
15+
dir string
16+
fs rwvfs.FileSystem
17+
}
18+
19+
func newTestServer() testServer {
20+
dir, err := ioutil.TempDir("", "httpfstream")
21+
if err != nil {
22+
panic("TempDir: " + err.Error())
23+
}
24+
err = os.MkdirAll(dir, 0700)
25+
if err != nil {
26+
panic("MkdirAll: " + err.Error())
27+
}
28+
29+
rootMux := http.NewServeMux()
30+
fs := rwvfs.OS(dir)
31+
rootMux.Handle("/", New(fs))
32+
return testServer{
33+
Server: httptest.NewServer(rootMux),
34+
dir: dir,
35+
fs: fs,
36+
}
37+
}
38+
39+
func (s testServer) close() {
40+
s.Server.Close()
41+
os.RemoveAll(s.dir)
42+
}
43+
44+
func readAll(t *testing.T, rdr io.ReadCloser) []byte {
45+
defer rdr.Close()
46+
data, err := ioutil.ReadAll(rdr)
47+
if err != nil {
48+
t.Fatal("ReadAll", err)
49+
}
50+
return data
51+
}

httpfstream.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package httpfstream
2+
3+
import (
4+
"code.google.com/p/go.tools/godoc/vfs/httpfs"
5+
"github.com/sourcegraph/rwvfs"
6+
"log"
7+
"net/http"
8+
)
9+
10+
func New(root rwvfs.FileSystem) handler {
11+
return handler{
12+
Root: root,
13+
httpFS: httpfs.New(root),
14+
}
15+
}
16+
17+
type handler struct {
18+
Root rwvfs.FileSystem
19+
Log *log.Logger
20+
21+
httpFS http.FileSystem
22+
}
23+
24+
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
25+
switch r.Method {
26+
case "GET":
27+
http.FileServer(h.httpFS).ServeHTTP(w, r)
28+
case "PUT":
29+
h.Upload(w, r)
30+
default:
31+
http.Error(w, "only GET or PUT methods are supported", http.StatusBadRequest)
32+
}
33+
}

upload.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package httpfstream
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"os"
7+
)
8+
9+
func (h handler) Upload(w http.ResponseWriter, r *http.Request) {
10+
if h.Log != nil {
11+
h.Log.Printf("PUT %s", r.URL.Path)
12+
}
13+
14+
defer r.Body.Close()
15+
16+
if r.URL.Path[len(r.URL.Path)-1] == '/' {
17+
http.Error(w, "path must not end with '/'", http.StatusBadRequest)
18+
return
19+
}
20+
21+
f, err := h.Root.OpenFile(r.URL.Path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL)
22+
if err != nil {
23+
http.Error(w, "failed to open destination file for writing: "+err.Error(), http.StatusInternalServerError)
24+
return
25+
}
26+
defer f.Close()
27+
28+
_, err = io.Copy(f, r.Body)
29+
if err != nil {
30+
http.Error(w, "failed to copy upload stream to file: "+err.Error(), http.StatusInternalServerError)
31+
return
32+
}
33+
34+
err = r.Body.Close()
35+
if err != nil {
36+
http.Error(w, "failed to close upload stream: "+err.Error(), http.StatusInternalServerError)
37+
return
38+
}
39+
40+
err = f.Close()
41+
if err != nil {
42+
http.Error(w, "failed to close destination file: "+err.Error(), http.StatusInternalServerError)
43+
return
44+
}
45+
}

upload_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package httpfstream
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"net/http"
7+
"strings"
8+
"testing"
9+
)
10+
11+
type uploadTest struct {
12+
path string
13+
data io.Reader
14+
15+
// error responses
16+
statusCode int
17+
msg string
18+
}
19+
20+
func TestUpload(t *testing.T) {
21+
tests := []uploadTest{
22+
{path: "/foo", data: bytes.NewReader([]byte("bar")), statusCode: http.StatusOK},
23+
{path: "/foo", statusCode: http.StatusOK},
24+
25+
{path: "/", statusCode: http.StatusBadRequest, msg: "path must not end with '/'"},
26+
{path: "/..", statusCode: http.StatusMovedPermanently},
27+
{path: "/../foo", statusCode: http.StatusMovedPermanently},
28+
}
29+
for _, test := range tests {
30+
testUpload(t, test)
31+
}
32+
}
33+
34+
func testUpload(t *testing.T, test uploadTest) {
35+
label := test.path
36+
37+
server := newTestServer()
38+
defer server.close()
39+
40+
req, err := http.NewRequest("PUT", server.URL+test.path, nil)
41+
if err != nil {
42+
t.Fatalf("%s: NewRequest: %s", label, err)
43+
}
44+
45+
resp, err := http.DefaultClient.Do(req)
46+
if err != nil {
47+
t.Fatalf("%s: Do: %s", label, err)
48+
}
49+
50+
if test.statusCode != resp.StatusCode {
51+
t.Errorf("%s: want StatusCode == %d, got %d", label, test.statusCode, resp.StatusCode)
52+
}
53+
54+
if test.statusCode >= 200 && test.statusCode <= 299 {
55+
_, err = server.fs.Stat(test.path)
56+
if err != nil {
57+
t.Errorf("%s: Stat: %s", label, err)
58+
}
59+
} else {
60+
msg := strings.TrimSpace(string(readAll(t, resp.Body)))
61+
if test.msg != msg {
62+
t.Errorf("%s: want error message %q, got %q", label, test.msg, msg)
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)