-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathrest.go
156 lines (141 loc) · 4.62 KB
/
rest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package slicesync
import (
"fmt"
"io"
"net/http"
"net/url"
"path"
"strconv"
"strings"
)
// -- Server Side --
// HashNServe starts both a hash service preparing hash dumps and an http to serve them and the normal files remotely
func HashNServe(port int, dir string, slice int64, recursive bool) {
go HashService(dir, slice, recursive, DEFAULT_PERIOD)
ServeHashNDump(port, dir, "/")
}
// SetupHashNDumpServer prepares a Handler for a HashNDumpServer
func SetupHashNDumpServer(dir, prefix string) http.Handler {
if !strings.HasSuffix(prefix, "/") {
prefix = "/" + prefix
}
if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
//fmt.Println("prefix:", prefix)
smux := http.NewServeMux()
smux.HandleFunc("/favicon.ico", http.NotFound)
smux.Handle(prefix, filter(http.StripPrefix(prefix, http.FileServer(http.Dir(dir)))))
//fmt.Printf("smux=%#v\n", smux)
return smux
}
// NewHashNDumpServer creates a new NewHashNDumpServer with the setup from SetupHashNDumpServer(dir,prefix)
func NewHashNDumpServer(port int, dir, prefix string) *http.Server {
return &http.Server{Addr: fmt.Sprintf(":%v", port), Handler: SetupHashNDumpServer(dir, prefix)}
}
// ServeHashNDump runs an HTTP Server created from NewHashNDumpServer to download Hashes and slice Dumps slices of files
func ServeHashNDump(port int, dir, prefix string) {
NewHashNDumpServer(port, dir, prefix).ListenAndServe()
}
func filter(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//fmt.Println("url=", r.URL)
h.ServeHTTP(w, r)
})
}
// -- Client Side --
// RemoteHashNDump implements HashNDumper service remotely through HTTP GET requests
type RemoteHashNDump struct {
Server string
}
// Hash returns the remote stream of hash slices
func (rhnd *RemoteHashNDump) Hash(filename string) (io.ReadCloser, error) {
r, _, e := get(calcUrl(rhnd.Server, SlicesyncFile(".", filename)), 0, 0)
return r, e
}
// Dump returns the contents of a remote slice of the file (or the full file)
func (rhnd *RemoteHashNDump) Dump(filename string, pos, slice int64) (io.ReadCloser, int64, error) {
rc, r, err := get(calcUrl(rhnd.Server, filename), pos, slice)
if err != nil {
return nil, 0, err
}
N, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
if err != nil {
return nil, 0, err
}
return rc, N, err
}
// probe detects the server base url and separates the server url and the filename for a given remote file url
func Probe(probedUrl string) (server, filename string, err error) {
if !strings.Contains(probedUrl, "://") {
probedUrl = "http://" + probedUrl
}
u, err := url.Parse(probedUrl)
if err != nil {
return "", "", err
}
//fmt.Println("Probe ", u)
fullpath := u.Path
u.Path = path.Join("/", SlicesyncDir)
//fmt.Println("Testing ", u.String())
err = head(u.String() + "/")
if err == nil {
u.Path = "/"
server = u.String()
filename = fullpath[1:]
//fmt.Println("Probe Result -> ", server, filename)
return
}
for candidate := path.Dir(fullpath); len(candidate) > 0 && candidate != "/"; candidate = path.Dir(candidate) {
u.Path = path.Join(candidate, "/", SlicesyncDir)
//fmt.Println("Testing ", u.String())
err = head(u.String() + "/")
if err == nil {
u.Path = candidate
server = u.String()
filename = fullpath[len(candidate)+1:]
//fmt.Println("Probe Result *-> ", server, filename)
return
}
}
u.Path = "/"
err = fmt.Errorf("Remote server %s does not seem to support slicesync! (last error was %v)", u, err)
return
}
// head tries to access a url and returns an error if something is wrong or nil if all was fine
func head(url string) error {
r, err := http.DefaultClient.Head(url)
if err != nil {
return err
}
if r.StatusCode != 200 {
return fmt.Errorf("Unexpected status %v!", r.Status)
}
return nil
}
// get a remote URL incoming stream
func get(url string, pos, slice int64) (io.ReadCloser, *http.Response, error) {
//fmt.Printf("get %s\n", url)
get, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
if pos != 0 || slice != 0 {
get.Header.Add("Range", fmt.Sprintf("bytes=%v-%v", pos, pos+slice-1))
}
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return fmt.Errorf("Check url %v, redirection should not be required!", url)
}
resp, err := http.DefaultClient.Do(get)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != 200 && resp.StatusCode != 206 {
return nil, nil, fmt.Errorf("Error " + resp.Status + " connecting to " + url)
}
return resp.Body, resp, nil
}
// calcUrl returns the Url for the remote file
func calcUrl(server, filename string) string {
return fmt.Sprintf("%s%s", server, filename)
}