Skip to content

Commit e667171

Browse files
committed
tracker/http: update local filesystem serving system
1 parent d3b0343 commit e667171

File tree

8 files changed

+124
-71
lines changed

8 files changed

+124
-71
lines changed

config/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ type Configuration struct {
2626
Read time.Duration
2727
Write time.Duration
2828
}
29-
Threads int
29+
Threads int
30+
ServePath string
3031
}
3132
UDP struct {
3233
Enabled bool

config/embed.go

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"embed"
55
"os"
66
"path/filepath"
7-
"strings"
87
"syscall"
98

109
"github.com/pkg/errors"
@@ -36,54 +35,3 @@ func writeEmbeddedConfig(path string) error {
3635

3736
return nil
3837
}
39-
40-
type EmbeddedCache map[string]string
41-
42-
func stripNewlineTabs(data string) string {
43-
data = strings.ReplaceAll(data, "\t", "")
44-
data = strings.ReplaceAll(data, "\n", "")
45-
return data
46-
}
47-
48-
func GenerateEmbeddedCache() (EmbeddedCache, error) {
49-
dir, err := embeddedFS.ReadDir("embedded")
50-
if err != nil {
51-
return nil, errors.Wrap(err, "failed to read embed directory to populate cache")
52-
}
53-
54-
cache := make(EmbeddedCache, len(dir))
55-
56-
for _, entry := range dir {
57-
if entry.IsDir() {
58-
continue
59-
}
60-
61-
filename := entry.Name()
62-
63-
// don't expose configuration
64-
if filename == "trakx.yaml" {
65-
continue
66-
}
67-
68-
data, err := embeddedFS.ReadFile("embedded/" + filename)
69-
if err != nil {
70-
return nil, errors.Wrapf(err, "failed to open file %v from embedded", "embedded/"+filename)
71-
}
72-
73-
// trim data from html files to save bandwidth
74-
dataStr := string(data)
75-
if strings.HasSuffix(filename, ".html") {
76-
dataStr = stripNewlineTabs(dataStr)
77-
}
78-
79-
zap.L().Debug("adding file to embedded cache", zap.String("filename", filename))
80-
cache["/"+filename] = dataStr
81-
}
82-
83-
// if index exists copy to /
84-
if indexData, ok := cache["/index.html"]; ok {
85-
cache["/"] = indexData
86-
}
87-
88-
return cache, nil
89-
}

refactoring.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44

55
## Current
66

7-
* Refactor HTTP tracker tests based on udp tracker tests
8-
* Add UDP tracker scrape tests
7+
* Refactor tracker main()
98

109
## Future
1110

12-
* Consider ripping out embedded HTTP files, a reverse proxy should handle that, can provide example with existing files and Caddyfile
13-
* Refactor tracker main()
1411
* Refactor controller / command line interface - KISS!
1512
* Deal w/ every TODO
1613
* Implement new features

tracker/http/helpers.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import (
88
)
99

1010
var (
11-
httpSuccessBytes = []byte("HTTP/1.1 200\r\n\r\n")
11+
httpSuccess = "HTTP/1.1 200 OK\r\n\r\n"
12+
httpSuccessBytes = []byte(httpSuccess)
1213
)
1314

1415
// string concats are optimized in go so these are faster than []byte appends etc.
1516

1617
func writeSuccess(c net.Conn, body string) {
17-
c.Write(utils.StringToBytesUnsafe("HTTP/1.1 200\r\n\r\n" + body))
18+
c.Write(utils.StringToBytesUnsafe(httpSuccess + body))
1819
}
1920

2021
func writeStatus(c net.Conn, status string) {

tracker/http/httptracker.go

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ import (
66
"net"
77
"net/http"
88
"net/netip"
9+
"os"
10+
"path"
11+
"path/filepath"
12+
"strconv"
13+
"strings"
914
"time"
1015

11-
"github.com/crimist/trakx/config"
1216
"github.com/crimist/trakx/stats"
1317
"github.com/crimist/trakx/storage"
1418
"github.com/crimist/trakx/tracker"
@@ -27,16 +31,17 @@ type Tracker struct {
2731
shutdown chan struct{}
2832
stats *stats.Statistics
2933
expvarHandler http.Handler
30-
embeddedCache config.EmbeddedCache
34+
servePath string
3135
}
3236

33-
func NewTracker(peerDB storage.Database, stats *stats.Statistics, config tracker.TrackerConfig) *Tracker {
37+
func NewTracker(peerDB storage.Database, servePath string, stats *stats.Statistics, config tracker.TrackerConfig) *Tracker {
3438
return &Tracker{
3539
config: config,
3640
peerdb: peerDB,
3741
shutdown: make(chan struct{}),
3842
stats: stats,
3943
expvarHandler: expvar.Handler(),
44+
servePath: servePath,
4045
}
4146
}
4247

@@ -51,11 +56,6 @@ func (tracker *Tracker) Serve(ip net.IP, port int, routines int) error {
5156
}
5257
zap.L().Debug("Serving HTTP tracker on", zap.String("address", listener.Addr().String()))
5358

54-
tracker.embeddedCache, err = config.GenerateEmbeddedCache()
55-
if err != nil {
56-
return errors.Wrap(err, "failed to generate embedded cache")
57-
}
58-
5959
// TODO: figure out what optimal number of goroutines is (benchmark)
6060
// Going to need to write a tool that can simulate a large number of clients
6161
for i := 0; i < routines; i++ {
@@ -222,10 +222,61 @@ func (tracker *Tracker) process(conn net.Conn, data []byte) {
222222
conn: conn,
223223
}, nil)
224224
default:
225-
if data, ok := tracker.embeddedCache[reqData.Path]; ok {
226-
writeSuccess(conn, data)
227-
} else {
225+
if tracker.servePath == "" {
228226
writeStatus(conn, "404")
227+
break
228+
}
229+
230+
cleanPath := filepath.FromSlash(path.Clean("/" + reqData.Path))
231+
filePath := filepath.Join(tracker.servePath, cleanPath)
232+
233+
if !strings.HasPrefix(filePath, tracker.servePath) {
234+
writeStatus(conn, "403")
235+
tracker.stats.ClientErrors.Add(1)
236+
break
237+
}
238+
239+
fileInfo, err := os.Stat(filePath)
240+
if err != nil {
241+
if os.IsNotExist(err) {
242+
writeStatus(conn, "404")
243+
} else {
244+
zap.L().Debug("Error accessing file", zap.Error(err), zap.String("path", filePath))
245+
writeStatus(conn, "500")
246+
}
247+
break
229248
}
249+
250+
if fileInfo.IsDir() {
251+
writeStatus(conn, "403")
252+
break
253+
}
254+
255+
fileContent, err := os.ReadFile(filePath)
256+
if err != nil {
257+
zap.L().Debug("Error reading file", zap.Error(err), zap.String("path", filePath))
258+
writeStatus(conn, "500")
259+
break
260+
}
261+
262+
contentType := "application/octet-stream"
263+
switch filepath.Ext(filePath) {
264+
case ".html", ".htm":
265+
contentType = "text/html"
266+
case ".css":
267+
contentType = "text/css"
268+
case ".js":
269+
contentType = "application/javascript"
270+
case ".jpg", ".jpeg":
271+
contentType = "image/jpeg"
272+
case ".png":
273+
contentType = "image/png"
274+
case ".gif":
275+
contentType = "image/gif"
276+
case ".txt":
277+
contentType = "text/plain"
278+
}
279+
280+
conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Type: " + contentType + "\r\nContent-Length: " + strconv.Itoa(len(fileContent)) + "\r\n\r\n" + string(fileContent)))
230281
}
231282
}

tracker/http/httptracker_test.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net"
66
"net/http"
77
"os"
8+
"path/filepath"
89
"testing"
910
"time"
1011

@@ -70,8 +71,13 @@ func TestMain(m *testing.M) {
7071
zap.L().Fatal("TCP tracker received shutdown")
7172
}
7273

74+
servePath, err := filepath.Abs(".")
75+
if err != nil {
76+
zap.L().Fatal("failed to get absolute path", zap.Error(err))
77+
}
78+
7379
stats := stats.NewStats(0)
74-
tracker := NewTracker(peerDB, stats, testTrackerConfig)
80+
tracker := NewTracker(peerDB, servePath, stats, testTrackerConfig)
7581
go func() {
7682
err = tracker.Serve(nil, testNetworkPort, 1)
7783
if err != nil {
@@ -162,3 +168,52 @@ func TestHeartbeat(t *testing.T) {
162168
t.Errorf("Expected code 200, got %d", resp.StatusCode)
163169
}
164170
}
171+
172+
func TestServe(t *testing.T) {
173+
resp, err := http.Get(fmt.Sprintf("http://%s:%d/.", testNetAddress4, testNetworkPort))
174+
if err != nil {
175+
t.Fatal(err)
176+
}
177+
178+
if resp.StatusCode != 403 {
179+
t.Errorf("Expected code 403, got %d", resp.StatusCode)
180+
}
181+
182+
resp, err = http.Get(fmt.Sprintf("http://%s:%d/../", testNetAddress4, testNetworkPort))
183+
if err != nil {
184+
t.Fatal(err)
185+
}
186+
187+
if resp.StatusCode != 403 {
188+
t.Errorf("Expected code 403, got %d", resp.StatusCode)
189+
}
190+
191+
resp, err = http.Get(fmt.Sprintf("http://%s:%d/../../../../../../../../../../../etc/passwd", testNetAddress4, testNetworkPort))
192+
if err != nil {
193+
t.Fatal(err)
194+
}
195+
196+
if resp.StatusCode != 404 {
197+
t.Errorf("Expected code 404, got %d", resp.StatusCode)
198+
}
199+
200+
resp, err = http.Get(fmt.Sprintf("http://%s:%d/httptracker_test.go", testNetAddress4, testNetworkPort))
201+
if err != nil {
202+
t.Fatal(err)
203+
}
204+
205+
if resp.StatusCode != 200 {
206+
t.Errorf("Expected code 200, got %d", resp.StatusCode)
207+
}
208+
209+
body := make([]byte, 65535)
210+
n, err := resp.Body.Read(body)
211+
if err != nil {
212+
t.Fatal("Failed to read response body", err)
213+
}
214+
body = body[:n]
215+
216+
if string(body[:13]) != "package http\n" {
217+
t.Errorf("Expected file to read 'package http\\n', got %s", body)
218+
}
219+
}

0 commit comments

Comments
 (0)