Repository navigation
Expand file tree
/
Copy pathserver.go
More file actions
136 lines (122 loc) · 3.47 KB
/
Copy pathserver.go
File metadata and controls
136 lines (122 loc) · 3.47 KB
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
package main
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/girish/storage/p2p"
"github.com/girish/storage/store"
)
type FileServerOpts struct {
StorageRoot string //StorageRoot is the folder on hard drive where this node will save files
HTTPAddr string
Peers []string
Transport p2p.Transport
Store *store.Store
}
// DataMessage is wire protocol payload!
type DataMessage struct {
Key string
Data []byte
}
type FileServer struct {
FileServerOpts
quitCh chan struct{}
}
func NewFileServer(opts FileServerOpts) *FileServer {
return &FileServer{
FileServerOpts: opts,
quitCh: make(chan struct{}),
}
}
// Start boots up the transport layer and begins processing messages
func (s *FileServer) Start() error {
fmt.Printf("Starting File Server... (Storage Root: %s)\n", s.StorageRoot)
if err := s.Transport.ListenAndAccept(); err != nil {
return err
}
go func() {
fmt.Printf("HTTP API listening on %s\n", s.HTTPAddr)
if err := http.ListenAndServe(s.HTTPAddr, s.httpHandler()); err != nil {
fmt.Printf("HTTP server error: %s\n", err)
}
}()
s.loop()
return nil
}
func (s *FileServer) httpHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("PUT /files/{key}", s.handleHTTPPut)
return mux
}
func (s *FileServer) handleHTTPPut(w http.ResponseWriter, r *http.Request) {
key := r.PathValue("key")
if key == "" {
http.Error(w, "file key is required", http.StatusBadRequest)
return
}
data, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read upload", http.StatusBadRequest)
return
}
if err := s.Store.WriteStream(key, bytes.NewReader(data)); err != nil {
http.Error(w, "failed to store file", http.StatusInternalServerError)
return
}
if r.Header.Get("X-DFS-Replication") != "true" {
client := &http.Client{Timeout: 10 * time.Second}
for _, peer := range s.Peers {
peer = strings.TrimRight(peer, "/")
peerURL := peer + "/files/" + url.PathEscape(key)
request, err := http.NewRequestWithContext(r.Context(), http.MethodPut, peerURL, bytes.NewReader(data))
if err != nil {
http.Error(w, "invalid peer URL", http.StatusBadGateway)
return
}
request.Header.Set("X-DFS-Replication", "true")
response, err := client.Do(request)
if err != nil {
http.Error(w, "failed to replicate to peer "+peer, http.StatusBadGateway)
return
}
response.Body.Close()
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
http.Error(w, "peer rejected replicated upload: "+peer, http.StatusBadGateway)
return
}
}
}
w.WriteHeader(http.StatusCreated)
}
func (s *FileServer) loop() {
for {
select {
case rpc := <-s.Transport.Consume():
s.handleMessage(rpc)
case <-s.quitCh:
fmt.Println("File Server shutting down")
return
}
}
}
func (s *FileServer) handleMessage(rpc p2p.RPC) {
//1 Decode the binary network payload back into Structured DataMessage
var msg DataMessage
if err := gob.NewDecoder(bytes.NewReader(rpc.Payload)).Decode(&msg); err != nil {
fmt.Printf("Failed to decode network payload: %s\n", err)
return
}
fmt.Printf("FileServer received command to store file: '%s' (%d bytes)\n", msg.Key, len(msg.Data))
//2 Write the file to disk using CAS store engine
err := s.Store.WriteStream(msg.Key, bytes.NewReader(msg.Data))
if err != nil {
fmt.Printf("Error Storing file to disk: %s\n", err)
return
}
fmt.Println("File successfully saved from the network!")
}