diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8c4c1ed --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build and Upload Artifacts +on: + push: + branches: [ main ] + +jobs: + build-and-upload: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Build Server + run: | + go build serve.go + echo "Server built successfully!" + ls + + - name: Build Client + run: | + cd client + go build fcdn.go + + - name: Upload Artifacts + uses: actions/upload-artifact@v2 + with: + name: server-and-client + path: | + ./server + ./client/fcdn diff --git a/README.md b/README.md new file mode 100644 index 0000000..447edb8 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# FCDN +Download the same file from 2 server for increase speed and low bandwidth usage + +Try Now: +Step 1: Download the fcdn client first +https://github.com/pawitpr/fcdn-client + +Step2: We have already setup the server for you (https://cdn1-osyp.onrender.com/) (https://cdn0.onrender.com/) +and this is a test command: +```bash +fcdn.exe -a rootfs.xz https://cdn1-osyp.onrender.com/cdn/ubuntu-rootfs-arm64.tar.xz https://cdn0.onrender.com/cdn/ubuntu-rootfs-arm64.tar.xz +``` + +# Demo Video +https://github.com/pawitpr/fcdn/assets/123424956/7c80fda9-787a-4ee3-baa2-1924d96b2d83 + diff --git a/cdn/2023-05-17 09-31-49.mp4 b/cdn/2023-05-17 09-31-49.mp4 new file mode 100644 index 0000000..c8d0e75 Binary files /dev/null and b/cdn/2023-05-17 09-31-49.mp4 differ diff --git a/client/fcdn.go b/client/fcdn.go new file mode 100644 index 0000000..61a1653 --- /dev/null +++ b/client/fcdn.go @@ -0,0 +1,95 @@ +package main + +import ( + "flag" + "fmt" + "io" + "net/http" + "os" + "sync" + "time" +) + +func download(url string, wg *sync.WaitGroup, dest string) { + defer wg.Done() + + startTime := time.Now() + + // Create a new HTTP GET request + resp, err := http.Get(url) + if err != nil { + fmt.Printf("Error downloading %s: %s\n", url, err) + return + } + defer resp.Body.Close() + + // Create a new file to save the downloaded data + file, err := os.Create(dest) + if err != nil { + fmt.Printf("Error creating file: %s\n", err) + return + } + defer file.Close() + + // Copy the response body to the file while measuring speed + _, err = io.Copy(file, io.TeeReader(resp.Body, &SpeedMeasurer{})) + if err != nil { + fmt.Printf("Error saving file: %s\n", err) + return + } + + elapsedTime := time.Since(startTime) + downloadSpeed := float64(resp.ContentLength) / elapsedTime.Seconds() / 1024 / 1024 // Speed in MB/s + + fmt.Printf("Downloaded %s\n", url) + fmt.Printf("Elapsed Time: %s\n", elapsedTime) + fmt.Printf("Download Speed: %.2f MB/s\n", downloadSpeed) +} + +type SpeedMeasurer struct { + totalBytes int64 + lastTime time.Time +} + +func (sm *SpeedMeasurer) Write(p []byte) (n int, err error) { + n = len(p) + sm.totalBytes += int64(n) + + if sm.lastTime.IsZero() { + sm.lastTime = time.Now() + } else { + elapsedTime := time.Since(sm.lastTime) + if elapsedTime.Seconds() >= 1 { + downloadSpeed := float64(sm.totalBytes) / elapsedTime.Seconds() / 1024 / 1024 // Speed in MB/s + fmt.Printf("Current Download Speed: %.2f MB/s\n", downloadSpeed) + sm.totalBytes = 0 + sm.lastTime = time.Now() + } + } + + return +} + +func main() { + // Parse command-line arguments + var fileName string + flag.StringVar(&fileName, "a", "file.txt", "Destination file name") + flag.Parse() + + // Get the server URLs from command-line arguments + urls := flag.Args() + if len(urls) == 0 { + fmt.Println("Please provide server URLs") + return + } + + var wg sync.WaitGroup + wg.Add(len(urls)) + + for _, url := range urls { + go download(url, &wg, fileName) + } + + wg.Wait() + fmt.Println("All downloads completed") +} diff --git a/client/go.mod b/client/go.mod new file mode 100644 index 0000000..32cee3f --- /dev/null +++ b/client/go.mod @@ -0,0 +1,3 @@ +module example.com/m + +go 1.20 diff --git a/client/key b/client/key new file mode 100644 index 0000000..25f4495 --- /dev/null +++ b/client/key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDTR0qHiT5bRaqJd1wuchb6owbjxHGIMfOT/SLlYG07lQAAAJjWZ3Ol1mdz +pQAAAAtzc2gtZWQyNTUxOQAAACDTR0qHiT5bRaqJd1wuchb6owbjxHGIMfOT/SLlYG07lQ +AAAED4vE/GvgmPUCeRRtgWzubzAXq+Ody24dnw6JH/3PrwoNNHSoeJPltFqol3XC5yFvqj +BuPEcYgx85P9IuVgbTuVAAAAFXBhd2l0c2FoYXJlQGdtYWlsLmNvbQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/client/key.pub b/client/key.pub new file mode 100644 index 0000000..49804a4 --- /dev/null +++ b/client/key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINNHSoeJPltFqol3XC5yFvqjBuPEcYgx85P9IuVgbTuV pawitsahare@gmail.com diff --git a/serve.go b/serve.go new file mode 100644 index 0000000..5c6d937 --- /dev/null +++ b/serve.go @@ -0,0 +1,27 @@ +/* +Serve is a very simple static file server in go +Usage: + -p="8100": port to serve on + -d=".": the directory of static files to host +Navigating to http://localhost:8100 will display the index.html or directory +listing file. +*/ +package main + +import ( + "flag" + "log" + "net/http" +) + +func main() { + port := flag.String("p", "8100", "port to serve on") + directory := flag.String("d", ".", "the directory of static file to host") + flag.Parse() + + http.Handle("/", http.FileServer(http.Dir(*directory))) + + log.Printf("Serving %s on HTTP port: %s\n", *directory, *port) + log.Fatal(http.ListenAndServe(":"+*port, nil)) +} +//