Skip to content

Commit 166c97c

Browse files
committed
kadai3-2-nKumaya
1 parent 1153a9d commit 166c97c

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

kadai3-2/nKumaya/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 課題3-2 分割ダウンロードを行う
2+
3+
## 分割ダウンロードの説明
4+
5+
```
6+
go run main.go [ターゲットURL]
7+
```
8+
対象URLをCPU数に応じて分割ダウンロードを行う。
9+
対象URLのステータスコードが200以外のときはエラーを返す。
10+

kadai3-2/nKumaya/kget/kget.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package kget
2+
import (
3+
"context"
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"net/url"
8+
"os"
9+
"runtime"
10+
"strconv"
11+
"strings"
12+
"golang.org/x/sync/errgroup"
13+
)
14+
type Client struct {
15+
URL *url.URL
16+
HTTPClient *http.Client
17+
ContentLength, RangeSize int
18+
Filename string
19+
}
20+
func NewClient(urlString string) (*Client, error) {
21+
var err error
22+
client := &Client{}
23+
client.URL, err = url.Parse(urlString)
24+
splitedPath := strings.Split(client.URL.Path, "/")
25+
client.Filename = splitedPath[len(splitedPath)-1]
26+
if err != nil {
27+
return nil, err
28+
}
29+
client.HTTPClient = &http.Client{}
30+
if err = client.setHeadContent(); err != nil {
31+
return nil, err
32+
}
33+
return client, nil
34+
}
35+
// レスポンスヘッダに Accept-Ranges が含まれている場合は分割, ない場合は分割しない
36+
func (c *Client) setHeadContent() error {
37+
req, err := http.NewRequest("HEAD", c.URL.String(), nil)
38+
if err != nil {
39+
return err
40+
}
41+
client := &http.Client{}
42+
res, err := client.Do(req)
43+
if err != nil {
44+
return err
45+
}
46+
defer res.Body.Close()
47+
c.ContentLength, err = strconv.Atoi(res.Header["Content-Length"][0])
48+
if err != nil {
49+
return err
50+
}
51+
if strings.Contains("bytes", res.Header["Accept-Ranges"][0]) {
52+
// CPU数に応じて並行処理を行う
53+
c.RangeSize = runtime.NumCPU()
54+
} else {
55+
c.RangeSize = 1
56+
}
57+
return nil
58+
}
59+
func (c *Client) newRequest(ctx context.Context, index int) (*http.Request, error) {
60+
req, err := http.NewRequest("GET", c.URL.String(), nil)
61+
if err != nil {
62+
return req, nil
63+
}
64+
req = req.WithContext(ctx)
65+
req.Header.Add("User-Agent", "kget")
66+
var chank int
67+
chank = c.ContentLength / c.RangeSize
68+
low := chank * index
69+
high := chank*(index+1) - 1
70+
if index+1 == c.RangeSize && high+1 != c.ContentLength {
71+
high = c.ContentLength - 1
72+
}
73+
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", low, high))
74+
return req, nil
75+
}
76+
func (c *Client) save(ctx context.Context, index int) error {
77+
req, err := c.newRequest(ctx, index)
78+
if err != nil {
79+
return err
80+
}
81+
file, err := os.Create(c.Filename + "_" + strconv.Itoa(index))
82+
if err != nil {
83+
return err
84+
}
85+
res, err := c.HTTPClient.Do(req)
86+
if err != nil {
87+
return err
88+
}
89+
_, err = io.Copy(file, res.Body)
90+
if err != nil {
91+
return err
92+
}
93+
file.Close()
94+
return nil
95+
}
96+
func (c *Client) merge(ctx context.Context) error {
97+
file, err := os.Create(c.Filename)
98+
if err != nil {
99+
return err
100+
}
101+
for i := 0; i < c.RangeSize; i++ {
102+
subFileName := c.Filename + "_" + strconv.Itoa(i)
103+
subFile, err := os.Open(subFileName)
104+
if err != nil {
105+
return err
106+
}
107+
_, err = io.Copy(file, subFile)
108+
subFile.Close()
109+
if err != nil {
110+
return err
111+
}
112+
}
113+
return nil
114+
}
115+
func (c *Client) Download(ctx context.Context) error {
116+
eg := errgroup.Group{}
117+
for i := 0; i < c.RangeSize; i++ {
118+
i := i
119+
eg.Go(func() error {
120+
return c.save(ctx, i)
121+
})
122+
}
123+
if err := eg.Wait(); err != nil {
124+
c.DeleteFiles()
125+
return err
126+
}
127+
err := c.merge(ctx)
128+
c.DeleteFiles()
129+
if err != nil {
130+
return err
131+
}
132+
return nil
133+
}
134+
func (c *Client) DeleteFiles() error {
135+
for i := 0; i < c.RangeSize; i++ {
136+
subFileName := c.Filename + "_" + strconv.Itoa(i)
137+
if fi, _ := os.Stat(subFileName); fi != nil {
138+
if err := os.Remove(subFileName); err != nil {
139+
return err
140+
}
141+
}
142+
}
143+
return nil
144+
}

kadai3-2/nKumaya/main.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
import (
3+
"context"
4+
"flag"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"os/signal"
10+
"github.com/dojo3/kadai3-2/nKumaya/kget"
11+
)
12+
const (
13+
ExitCodeOK = iota
14+
ExitCodeParseFlagError
15+
ExitCodeInvalidUrlError
16+
ExitCodeCreateHTTPClient
17+
ExitCodeErrorDownload
18+
ExitCodeErrorCansel
19+
)
20+
type CLI struct {
21+
outStream, errStream io.Writer
22+
}
23+
func (c *CLI) Run(args []string) int {
24+
flags := flag.NewFlagSet("kget", flag.ContinueOnError)
25+
if err := flags.Parse(args[1:]); err != nil {
26+
return ExitCodeParseFlagError
27+
}
28+
url := args[1]
29+
fmt.Fprintln(c.outStream, "Checking now", url)
30+
response, err := http.Get(url)
31+
fmt.Println(response.StatusCode)
32+
if response.StatusCode != 200 {
33+
return ExitCodeInvalidUrlError
34+
}
35+
client, err := kget.NewClient(url)
36+
if err != nil {
37+
return ExitCodeCreateHTTPClient
38+
}
39+
bc := context.Background()
40+
ctx, cancel := context.WithCancel(bc)
41+
ch := make(chan os.Signal, 1)
42+
signal.Notify(ch, os.Interrupt)
43+
defer func() {
44+
signal.Stop(ch)
45+
cancel()
46+
}()
47+
fmt.Fprintln(c.outStream, "Download start", url)
48+
go func() error {
49+
select {
50+
case <-ch:
51+
cancel()
52+
return nil
53+
case <-ctx.Done():
54+
if err = client.DeleteFiles(); err != nil {
55+
return err
56+
}
57+
return nil
58+
}
59+
}()
60+
err = client.Download(ctx)
61+
if err != nil {
62+
return ExitCodeErrorDownload
63+
}
64+
fmt.Fprintln(c.outStream, "Complite")
65+
return ExitCodeOK
66+
}
67+
func main() {
68+
cli := &CLI{os.Stdout, os.Stderr}
69+
os.Exit(cli.Run(os.Args))
70+
}

0 commit comments

Comments
 (0)