Last active
June 14, 2022 02:44
-
-
Save ayanamist/a900832af9c8b95b489875a94a13a842 to your computer and use it in GitHub Desktop.
一个特殊的MITM http proxy,将图片、js、css请求劫持并缓存在本地,后续再访问时直接从本地返回,避免一些垃圾网站打不开的问题
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"compress/gzip" | |
"errors" | |
"flag" | |
"io" | |
"io/ioutil" | |
"log" | |
"mime" | |
"net" | |
"net/http" | |
"os" | |
"path/filepath" | |
"time" | |
) | |
var dialer = &net.Dialer{Timeout: 10 * time.Second, KeepAlive: 30 * time.Second} | |
var tr = &http.Transport{DialContext: dialer.DialContext} | |
var cacheDir string | |
func cacheable(r http.Request) bool { | |
if r.Method != http.MethodGet { | |
return false | |
} | |
switch filepath.Ext(r.URL.Path) { | |
case ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".woff", ".woff2", ".ttf", ".eot", ".ico": | |
return true | |
default: | |
return false | |
} | |
} | |
func handleFunc(w http.ResponseWriter, r *http.Request) { | |
log.Printf("%s: %s %+v", r.RemoteAddr, r.Method, r.URL) | |
isCacheable := cacheable(*r) | |
p := r.URL.EscapedPath() | |
if r.URL.RawQuery != "" { | |
p += "?" + r.URL.RawQuery | |
} | |
cachePath := filepath.Join(cacheDir, r.URL.Host, p) | |
if isCacheable { | |
f, err := os.Open(cachePath) | |
if err != nil { | |
if errors.Is(err, os.ErrNotExist) { | |
log.Printf("%s: %s: cache miss", r.RemoteAddr, cachePath) | |
} else { | |
log.Printf("%s: %s: failed to open cache file: %v", r.RemoteAddr, cachePath, err) | |
} | |
} else { | |
log.Printf("%s: %s: cache hit", r.RemoteAddr, cachePath) | |
defer f.Close() | |
if mimeType := mime.TypeByExtension(filepath.Ext(cachePath)); mimeType != "" { | |
w.Header().Set("Content-Type", mimeType) | |
} | |
if _, err = io.Copy(w, f); err != nil { | |
log.Printf("%s: %s: failed to copy cache file: %v", r.RemoteAddr, cachePath, err) | |
} | |
return | |
} | |
} | |
resp, err := tr.RoundTrip(r) | |
if err != nil { | |
w.WriteHeader(http.StatusBadGateway) | |
w.Write([]byte(err.Error())) | |
return | |
} | |
wHeader := w.Header() | |
for k, v := range resp.Header { | |
wHeader[k] = v | |
} | |
w.WriteHeader(resp.StatusCode) | |
var dst io.Writer = w | |
buf := &bytes.Buffer{} | |
if isCacheable { | |
dst = io.MultiWriter(w, buf) | |
} | |
if _, err = io.Copy(dst, resp.Body); err != nil { | |
log.Printf("%s: failed to copy response body: %v", r.RemoteAddr, err) | |
} else if isCacheable { | |
if err := os.MkdirAll(filepath.Dir(cachePath), 0755); err != nil { | |
log.Printf("%s: failed to create cache directory: %v", r.RemoteAddr, err) | |
} else { | |
if resp.Header.Get("Content-Encoding") == "gzip" { | |
if reader, err := gzip.NewReader(buf); err != nil { | |
log.Printf("%s: failed to create gzip reader: %v", r.RemoteAddr, err) | |
} else { | |
buf2 := &bytes.Buffer{} | |
if _, err := io.Copy(buf2, reader); err != nil { | |
log.Printf("%s: failed to copy gzip reader: %v", r.RemoteAddr, err) | |
} | |
buf = buf2 | |
} | |
} | |
if err := ioutil.WriteFile(cachePath, buf.Bytes(), 0644); err != nil { | |
log.Printf("%s: failed to write cache file: %v", r.RemoteAddr, err) | |
} | |
} | |
} | |
} | |
func main() { | |
var listenAddr string | |
const defaultListen = "localhost:8888" | |
flag.StringVar(&listenAddr, "listen", defaultListen, "address to listen on, default is "+defaultListen) | |
cacheDir = filepath.Join(os.TempDir(), "cache") | |
flag.StringVar(&cacheDir, "cache", cacheDir, "directory to cache responses, default is "+cacheDir) | |
flag.Parse() | |
log.Printf("try listen on %s cache on %s", listenAddr, cacheDir) | |
if err := http.ListenAndServe(listenAddr, http.HandlerFunc(handleFunc)); err != nil { | |
log.Fatalf("failed to listen and serve: %v", err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment