diff --git a/main.go b/main.go index ebb1620..2c07154 100644 --- a/main.go +++ b/main.go @@ -158,10 +158,13 @@ type responseLogger struct { http.ResponseWriter } +// WriteHeader writes header code into responser writer. func (r *responseLogger) WriteHeader(code int) { r.code = code r.ResponseWriter.WriteHeader(code) } + +// ServeHTTP implements http handler. func (l *logger) ServeHTTP(w http.ResponseWriter, r *http.Request) { start := time.Now() rl := &responseLogger{code: 200, ResponseWriter: w} @@ -172,9 +175,12 @@ func (l *logger) ServeHTTP(w http.ResponseWriter, r *http.Request) { // An ops is a proxy.ServerOps implementation. type ops struct{} +// NewContext crates a context. func (*ops) NewContext(r *http.Request) (context.Context, error) { return context.Background(), nil } + +// List lists proxy files. func (*ops) List(ctx context.Context, mpath string) (proxy.File, error) { escMod, err := module.EscapePath(mpath) if err != nil { @@ -209,6 +215,8 @@ func (*ops) List(ctx context.Context, mpath string) (proxy.File, error) { return os.Open(file) } + +// Latest fetch latest file. func (*ops) Latest(ctx context.Context, path string) (proxy.File, error) { d, err := download(module.Version{Path: path, Version: "latest"}) if err != nil { @@ -216,6 +224,8 @@ func (*ops) Latest(ctx context.Context, path string) (proxy.File, error) { } return os.Open(d.Info) } + +// Info fetch info file. func (*ops) Info(ctx context.Context, m module.Version) (proxy.File, error) { d, err := download(m) if err != nil { @@ -223,6 +233,8 @@ func (*ops) Info(ctx context.Context, m module.Version) (proxy.File, error) { } return os.Open(d.Info) } + +// GoMod fetch go mod file. func (*ops) GoMod(ctx context.Context, m module.Version) (proxy.File, error) { d, err := download(m) if err != nil { @@ -230,6 +242,8 @@ func (*ops) GoMod(ctx context.Context, m module.Version) (proxy.File, error) { } return os.Open(d.GoMod) } + +// Zip fetch zip file. func (*ops) Zip(ctx context.Context, m module.Version) (proxy.File, error) { d, err := download(m) if err != nil { diff --git a/proxy/router.go b/proxy/router.go index ca13662..8780fe4 100644 --- a/proxy/router.go +++ b/proxy/router.go @@ -34,16 +34,98 @@ type RouterOptions struct { // which implements Route Filter to // routing private module or public module . type Router struct { + opts *RouterOptions srv *Server proxy *httputil.ReverseProxy pattern string downloadRoot string } +func (router *Router) customModResponse(r *http.Response) error { + var err error + if r.StatusCode == http.StatusOK { + var buf []byte + if strings.Contains(r.Header.Get("Content-Encoding"), "gzip") { + gr, err := gzip.NewReader(r.Body) + if err != nil { + return err + } + defer gr.Close() + buf, err = ioutil.ReadAll(gr) + if err != nil { + return err + } + r.Header.Del("Content-Encoding") + } else { + buf, err = ioutil.ReadAll(r.Body) + if err != nil { + return err + } + } + r.Body = ioutil.NopCloser(bytes.NewReader(buf)) + if buf != nil { + file := filepath.Join(router.opts.DownloadRoot, r.Request.URL.Path) + os.MkdirAll(path.Dir(file), os.ModePerm) + err = renameio.WriteFile(file, buf, 0666) + if err != nil { + return err + } + } + } + // support 302 status code. + if r.StatusCode == http.StatusFound { + loc := r.Header.Get("Location") + if loc == "" { + return fmt.Errorf("%d response missing Location header", r.StatusCode) + } + + // TODO: location is relative. + _, err := url.Parse(loc) + if err != nil { + return fmt.Errorf("failed to parse Location header %q: %v", loc, err) + } + resp, err := http.Get(loc) + if err != nil { + return err + } + defer resp.Body.Close() + + var buf []byte + if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") { + gr, err := gzip.NewReader(resp.Body) + if err != nil { + return err + } + defer gr.Close() + buf, err = ioutil.ReadAll(gr) + if err != nil { + return err + } + resp.Header.Del("Content-Encoding") + } else { + buf, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + } + resp.Body = ioutil.NopCloser(bytes.NewReader(buf)) + if buf != nil { + file := filepath.Join(router.opts.DownloadRoot, r.Request.URL.Path) + os.MkdirAll(path.Dir(file), os.ModePerm) + err = renameio.WriteFile(file, buf, 0666) + if err != nil { + return err + } + } + } + return nil +} + // NewRouter returns a new Router using the given operations. func NewRouter(srv *Server, opts *RouterOptions) *Router { rt := &Router{ - srv: srv, + opts: opts, + srv: srv, } if opts != nil { if opts.Proxy == "" { @@ -61,91 +143,14 @@ func NewRouter(srv *Server, opts *RouterOptions) *Router { director(r) r.Host = remote.Host } + rt.proxy = proxy rt.proxy.Transport = &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } - rt.proxy.ModifyResponse = func(r *http.Response) error { - if r.StatusCode == http.StatusOK { - var buf []byte - if strings.Contains(r.Header.Get("Content-Encoding"), "gzip") { - gr, err := gzip.NewReader(r.Body) - if err != nil { - return err - } - defer gr.Close() - buf, err = ioutil.ReadAll(gr) - if err != nil { - return err - } - r.Header.Del("Content-Encoding") - } else { - buf, err = ioutil.ReadAll(r.Body) - if err != nil { - return err - } - } - r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - if buf != nil { - file := filepath.Join(opts.DownloadRoot, r.Request.URL.Path) - os.MkdirAll(path.Dir(file), os.ModePerm) - err = renameio.WriteFile(file, buf, 0666) - if err != nil { - return err - } - } - } - - // support 302 status code. - if r.StatusCode == http.StatusFound { - loc := r.Header.Get("Location") - if loc == "" { - return fmt.Errorf("%d response missing Location header", r.StatusCode) - } - - // TODO: location is relative. - _, err := url.Parse(loc) - if err != nil { - return fmt.Errorf("failed to parse Location header %q: %v", loc, err) - } - resp, err := http.Get(loc) - if err != nil { - return err - } - defer resp.Body.Close() - - var buf []byte - if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") { - gr, err := gzip.NewReader(resp.Body) - if err != nil { - return err - } - defer gr.Close() - buf, err = ioutil.ReadAll(gr) - if err != nil { - return err - } - resp.Header.Del("Content-Encoding") - } else { - buf, err = ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - } - resp.Body = ioutil.NopCloser(bytes.NewReader(buf)) - if buf != nil { - file := filepath.Join(opts.DownloadRoot, r.Request.URL.Path) - os.MkdirAll(path.Dir(file), os.ModePerm) - err = renameio.WriteFile(file, buf, 0666) - if err != nil { - return err - } - } - } - return nil - } + rt.proxy.ModifyResponse = rt.customModResponse rt.pattern = opts.Pattern rt.downloadRoot = opts.DownloadRoot } @@ -160,6 +165,7 @@ func (rt *Router) Direct(path string) bool { return GlobsMatchPath(rt.pattern, path) } +// ServveHTTP implements http handler. func (rt *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { // sumdb handler if strings.HasPrefix(r.URL.Path, "/sumdb/") { diff --git a/proxy/server.go b/proxy/server.go index c3bbbd3..95f4180 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -221,8 +221,13 @@ type memFile struct { stat memStat } -func (f *memFile) Close() error { return nil } -func (f *memFile) Stat() (os.FileInfo, error) { return &f.stat, nil } +// Close closes file. +func (f *memFile) Close() error { return nil } + +// Stat stats file. +func (f *memFile) Stat() (os.FileInfo, error) { return &f.stat, nil } + +// Readdir read dir. func (f *memFile) Readdir(count int) ([]os.FileInfo, error) { return nil, os.ErrInvalid } type memStat struct { @@ -230,12 +235,23 @@ type memStat struct { size int64 } -func (s *memStat) Name() string { return "memfile" } -func (s *memStat) Size() int64 { return s.size } -func (s *memStat) Mode() os.FileMode { return 0444 } +// Name returns file name. +func (s *memStat) Name() string { return "memfile" } + +// Size returns file size. +func (s *memStat) Size() int64 { return s.size } + +// Mode returns file mode. +func (s *memStat) Mode() os.FileMode { return 0444 } + +// ModTime returns file modtime. func (s *memStat) ModTime() time.Time { return s.t } -func (s *memStat) IsDir() bool { return false } -func (s *memStat) Sys() interface{} { return nil } + +// IsDir returns if file is a dir. +func (s *memStat) IsDir() bool { return false } + +// Sys return nil. +func (s *memStat) Sys() interface{} { return nil } // NewInfo returns a formatted info file for the given version, time pair. // The version should be a canonical semantic version. diff --git a/sumdb/handler.go b/sumdb/handler.go index 2787c8a..fbb9ad0 100644 --- a/sumdb/handler.go +++ b/sumdb/handler.go @@ -50,30 +50,15 @@ func Handler(w http.ResponseWriter, r *http.Request) { } p := "https://" + strings.TrimPrefix(r.URL.Path, "/sumdb/") - _, err := url.Parse(p) - if err != nil { - w.WriteHeader(http.StatusGone) - fmt.Fprintf(w, err.Error()) - return - } - - resp, err := http.Get(p) - if err != nil { - w.WriteHeader(http.StatusGone) - fmt.Fprintf(w, err.Error()) - return - } - defer resp.Body.Close() - w.WriteHeader(resp.StatusCode) - if _, err := io.Copy(w, resp.Body); err != nil { - fmt.Fprintf(w, err.Error()) - return - } - return + proxySumdb(p, w, r) } func sumViaGoproxy(w http.ResponseWriter, r *http.Request) { p := "https://goproxy.io" + r.URL.Path + proxySumdb(p, w, r) +} + +func proxySumdb(p string, w http.ResponseWriter, r *http.Request) { _, err := url.Parse(p) if err != nil { w.WriteHeader(http.StatusGone) @@ -94,4 +79,5 @@ func sumViaGoproxy(w http.ResponseWriter, r *http.Request) { return } return + }