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

Commit 73372e9

Browse files
committed
Many changes
1 parent 5550d13 commit 73372e9

File tree

10 files changed

+602
-225
lines changed

10 files changed

+602
-225
lines changed

client.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package httpfstream
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"github.com/garyburd/go-websocket/websocket"
8+
"io"
9+
"net"
10+
"net/http"
11+
"net/url"
12+
"os"
13+
"time"
14+
)
15+
16+
func Get(u *url.URL) (io.ReadCloser, error) {
17+
req, err := http.NewRequest("HEAD", u.String(), nil)
18+
if err != nil {
19+
return nil, err
20+
}
21+
resp, err := http.DefaultClient.Do(req)
22+
if err != nil {
23+
return nil, err
24+
}
25+
if resp.StatusCode != http.StatusOK {
26+
switch resp.StatusCode {
27+
case http.StatusNotFound:
28+
return nil, os.ErrNotExist
29+
default:
30+
return nil, fmt.Errorf("head failed with status %d", resp.StatusCode)
31+
}
32+
}
33+
34+
ws, err := newClient(u, "GET")
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
op, r, err := ws.NextReader()
40+
if err != nil {
41+
return nil, err
42+
}
43+
if op != websocket.OpText {
44+
return nil, errors.New("websocket op is not text")
45+
}
46+
47+
return &webSocketReadCloser{r, ws}, nil
48+
}
49+
50+
type webSocketReadCloser struct {
51+
io.Reader
52+
ws *websocket.Conn
53+
}
54+
55+
func (r *webSocketReadCloser) Read(p []byte) (n int, err error) {
56+
return r.Reader.Read(p)
57+
}
58+
59+
func (r *webSocketReadCloser) Close() error {
60+
return r.ws.Close()
61+
}
62+
63+
func Put(u *url.URL, r io.Reader) error {
64+
w, err := OpenPut(u)
65+
if err != nil {
66+
return err
67+
}
68+
defer w.Close()
69+
70+
_, err = io.Copy(w, r)
71+
return err
72+
}
73+
74+
func OpenPut(u *url.URL) (io.WriteCloser, error) {
75+
req, err := http.NewRequest("HEAD", u.String(), nil)
76+
if err != nil {
77+
return nil, err
78+
}
79+
resp, err := http.DefaultClient.Do(req)
80+
if err != nil {
81+
return nil, err
82+
}
83+
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {
84+
switch resp.StatusCode {
85+
default:
86+
return nil, fmt.Errorf("head failed with status %d", resp.StatusCode)
87+
}
88+
}
89+
90+
ws, err := newClient(u, "PUT")
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
return &putWriteCloser{new(bytes.Buffer), ws}, nil
96+
}
97+
98+
type putWriteCloser struct {
99+
io.Writer
100+
ws *websocket.Conn
101+
}
102+
103+
func (pw *putWriteCloser) Write(p []byte) (n int, err error) {
104+
pw.ws.SetWriteDeadline(time.Now().Add(writeWait))
105+
w, err := pw.ws.NextWriter(websocket.OpText)
106+
if err != nil {
107+
return 0, err
108+
}
109+
defer w.Close()
110+
return w.Write(p)
111+
}
112+
113+
func (pw *putWriteCloser) Close() error {
114+
return pw.ws.Close()
115+
}
116+
117+
func newClient(u *url.URL, method string) (*websocket.Conn, error) {
118+
c, err := net.Dial("tcp", u.Host)
119+
if err != nil {
120+
return nil, err
121+
}
122+
ws, _, err := websocket.NewClient(c, u, http.Header{xMethod: []string{method}}, readBufSize, writeBufSize)
123+
return ws, err
124+
}

client_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package httpfstream
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"net/url"
7+
"os"
8+
"testing"
9+
"time"
10+
)
11+
12+
type getTest struct {
13+
path string
14+
body string
15+
writeFiles map[string]string
16+
err error
17+
}
18+
19+
func TestGet(t *testing.T) {
20+
tests := []getTest{
21+
{path: "/foo1", body: "bar", writeFiles: map[string]string{"/foo1": "bar"}},
22+
23+
{path: "/doesntexist", err: os.ErrNotExist},
24+
}
25+
for _, test := range tests {
26+
testGet(t, test)
27+
}
28+
}
29+
30+
func testGet(t *testing.T, test getTest) {
31+
label := test.path
32+
33+
server := newTestServer()
34+
defer server.close()
35+
36+
for path, data := range test.writeFiles {
37+
w, err := server.fs.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL)
38+
if err != nil {
39+
t.Fatalf("%s: fs.WriterOpen: %s", label, err)
40+
}
41+
_, err = w.Write([]byte(data))
42+
if err != nil {
43+
t.Fatalf("%s: Write: %s", label, err)
44+
}
45+
err = w.Close()
46+
if err != nil {
47+
t.Fatalf("%s: Close: %s", label, err)
48+
}
49+
}
50+
51+
u, _ := url.Parse(server.URL + test.path)
52+
r, err := Get(u)
53+
if err == nil {
54+
defer r.Close()
55+
}
56+
if test.err != err {
57+
t.Errorf("%s: Get: want error %v, got %v", label, test.err, err)
58+
return
59+
}
60+
if test.err != nil {
61+
return
62+
}
63+
64+
body := string(readAll(t, r))
65+
if test.body != body {
66+
t.Errorf("%s: want body == %q, got %q", label, test.body, body)
67+
}
68+
}
69+
70+
type putTest struct {
71+
path string
72+
data io.Reader
73+
fileData string
74+
err error
75+
}
76+
77+
func TestPut(t *testing.T) {
78+
tests := []putTest{
79+
{path: "/foo1", data: bytes.NewReader([]byte("bar")), fileData: "bar"},
80+
{path: "/foo2", data: bytes.NewBuffer([]byte("bar")), fileData: "bar"},
81+
{path: "/foo3", data: &slowReader{R: &fixedReader{R: bytes.NewReader([]byte("quxx")), N: 2}, Wait: time.Millisecond * 25}, fileData: "quxx"},
82+
}
83+
for _, test := range tests {
84+
testPut(t, test)
85+
}
86+
}
87+
88+
func testPut(t *testing.T, test putTest) {
89+
label := test.path
90+
91+
server := newTestServer()
92+
defer server.close()
93+
94+
u, _ := url.Parse(server.URL + test.path)
95+
err := Put(u, test.data)
96+
if test.err != err {
97+
t.Errorf("%s: Put: want error %v, got %v", label, test.err, err)
98+
return
99+
}
100+
if test.err != nil {
101+
return
102+
}
103+
104+
time.Sleep(50 * time.Millisecond)
105+
106+
_, err = server.fs.Stat(test.path)
107+
if err != nil {
108+
t.Errorf("%s: Stat: %s", label, err)
109+
return
110+
}
111+
112+
f, err := server.fs.Open(test.path)
113+
if err != nil {
114+
t.Errorf("%s: Open: %s", label, err)
115+
return
116+
}
117+
fileData := string(readAll(t, f))
118+
if test.fileData != fileData {
119+
t.Errorf("%s: want fileData == %q, got %q", label, test.fileData, fileData)
120+
}
121+
}
122+
123+
type slowReader struct {
124+
R io.Reader
125+
Wait time.Duration
126+
afterRead func(read []byte)
127+
}
128+
129+
func (r *slowReader) Read(p []byte) (n int, err error) {
130+
if r.afterRead != nil {
131+
defer func() {
132+
r.afterRead(p)
133+
}()
134+
}
135+
time.Sleep(r.Wait)
136+
return r.R.Read(p)
137+
}
138+
139+
type fixedReader struct {
140+
R io.Reader
141+
N int
142+
}
143+
144+
func (r *fixedReader) Read(p []byte) (n int, err error) {
145+
return io.LimitReader(r.R, int64(r.N)).Read(p)
146+
}

cmd/httpfstream-server/server.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"github.com/gorilla/handlers"
7+
"github.com/sourcegraph/httpfstream"
8+
"github.com/sourcegraph/rwvfs"
9+
"log"
10+
"net/http"
11+
"os"
12+
)
13+
14+
var bindAddr = flag.String("http", ":8080", "HTTP bind address for server")
15+
var root = flag.String("root", "/tmp/httpfstream", "storage root directory")
16+
17+
func main() {
18+
flag.Usage = func() {
19+
fmt.Fprintf(os.Stderr, "httpfstream-server supports simultaneous, streaming file uploading and downloading over HTTP.\n\n")
20+
fmt.Fprintf(os.Stderr, "Usage:\n\n")
21+
fmt.Fprintf(os.Stderr, "\thttpfstream-server [options]\n\n")
22+
fmt.Fprintf(os.Stderr, "The options are:\n\n")
23+
flag.PrintDefaults()
24+
fmt.Fprintln(os.Stderr)
25+
fmt.Fprintln(os.Stderr)
26+
fmt.Fprintf(os.Stderr, "Example usage:\n\n")
27+
fmt.Fprintf(os.Stderr, "\tTo run on http://localhost:8080:\n")
28+
fmt.Fprintf(os.Stderr, "\t $ httpfstream-server -http=:8080\n\n")
29+
fmt.Fprintln(os.Stderr)
30+
os.Exit(1)
31+
}
32+
flag.Parse()
33+
if flag.NArg() != 0 {
34+
flag.Usage()
35+
}
36+
37+
os.MkdirAll(*root, 0700)
38+
39+
h := httpfstream.New(rwvfs.OS(*root))
40+
h.Log = log.New(os.Stderr, "", 0)
41+
http.Handle("/", handlers.CombinedLoggingHandler(os.Stdout, h))
42+
43+
log.Printf("Starting server on %s\n", *bindAddr)
44+
err := http.ListenAndServe(*bindAddr, nil)
45+
if err != nil {
46+
log.Fatalf("ListenAndServe: %s", err)
47+
}
48+
}

download_test.go

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)