简介
朋友提供了一个需求,由于互联网一些媒体资源出现了网络抖动,导致在某些时刻出现了媒体信息无法加载,由于加载的是动态图床,数量也比较大,在寻找了大量的现有解决方法之后,最终想到使用代理缓存的方式。
试用nginx
最开始想到的就是使用nginx进行解决,但由于存在多个域名,而手里只有一个ip,也不想写lua脚本,网络抖动时会返回302重定向;故放弃nginx的方案。
编程解决
经过一段时间的思想抗争,最终还是选择使用编程解决,思索再三,使用Golang解决,原因有几点,一是Golang支持多系统无障碍运行,二是Golang网络默认官方就带有反向代理的包,三是Golang实现很快。
整理思路
首先创建http服务->然后拦截所有请求->接收到请求判断是否存在缓存->存在则直接发返回缓存数据->不存在则使用代理请求服务,然后在返回结果。
主要功能
- 实现http反向代理
- 对响应数据进行区别处理
- 如果为image则缓存response
- 如果为其他类型则跳过
入口函数
func main() {
//创建日志文件
file, err := mkFile("httpProxy.log")
if err != nil {
log.Fatalln("fail to create httpProxy.log file!")
}
logger = log.New(file, "", log.Llongfile)
logger.SetFlags(log.LstdFlags)
// 初始化代理计数
proxyCount=0
flag.IntVar(&port, "P", 80, "端口号,默认为80")
flag.StringVar(&host, "H", "0.0.0.0", "主机地址,默认为0.0.0.0")
flag.StringVar(&internetIP, "I", "localhost", "主机公网地址,默认为")
flag.Parse()
// 获取本地IP
localIPs=getLocalIps()
localIPs=append(localIPs,internetIP)
// 创建反向代理缓存
domain=make(map[string]*httputil.ReverseProxy)
// 创建http 服务
http.HandleFunc("/", proxyRequestHandler)
hostStr:=host+":"+strconv.Itoa(port)
logger.Println("启动host",hostStr)
// 启动http 服务
logger.Fatal(http.ListenAndServe(hostStr, nil))
}
创建反向代理
// NewProxy 创建反向代理
func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
url, err := url.Parse(targetHost)
if err != nil {
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(url)
var DefaultTransport http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConnsPerHost: 20,
}
proxy.Transport=DefaultTransport
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
modifyRequest(req)
}
proxy.ModifyResponse = modifyResponse()
proxy.ErrorHandler = errorHandler()
return proxy, nil
}
修改http响应数据
// responseModify 修改http响应数据
func responseModify(r *http.Response) error {
//获取返回请求的内容类型
typeStr:=r.Header.Get("Content-Type")
//如果为图片
if strings.Contains(typeStr, "image"){
if r.StatusCode != 200 {
logger.Fatal("下载文件失败,http状态码为:",r.StatusCode,"响应内容为:",r.Body)
return nil
}
//缓存文件
querys:=r.Request.URL.Query()
args:=querys.Get("x")+querys.Get("y")+querys.Get("z")+querys.Get("ak")+querys.Get("styles")
js:=matchFile(md5V(args))
f, err := os.Create(js)
if err==nil{
res,err:=httputil.DumpResponse(r,true)
if err==nil{
f.Write(res)
if err1 := f.Close(); err == nil {
err = err1
}
}
}
}
//累计代理次数
proxyCount++
logger.Println("代理请求次数:",proxyCount)
if proxyCount>1000000{
proxyCount=0
logger.Println("代理超过:1000000 重置为0")
}
//logger.Println("完成请求:",r.Request.RequestURI)
return nil
}
返回缓存
// executeCache 执行缓存
func executeCache(f string,w http.ResponseWriter, r *http.Request){
file,_:=os.Open(f)
buff,err2:=ioutil.ReadAll(file)
if err2==nil{
temr := bufio.NewReader(bytes.NewReader(buff))
resp, err3 := http.ReadResponse(temr, nil)
if err3 == nil{
for k,vv:=range resp.Header{
for _,v:=range vv{
w.Header().Add(k,v)
}
}
bufio.NewReader(resp.Body).WriteTo(w)
cacheCount++
logger.Println("缓存击中一次",f,"缓存击中共计次数:",cacheCount)
if cacheCount>1000000{
cacheCount=0
logger.Println("缓存击中超过:1000000 重置为0")
}
}else{
w.WriteHeader(400)
w.Write([]byte(err3.Error()))
}
}else{
// 使用缓存
executeProxy(w,r)
}
}
完整代码
package main
import (
"bufio"
"bytes"
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path"
"strconv"
"strings"
"time"
)
var logger *log.Logger
var domain map[string]*httputil.ReverseProxy
var h = sha1.New()
var port int
var host string
var internetIP string
var proxyCount int64
var cacheCount int64
var localIPs []string
// NewProxy 创建反向代理
func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
url, err := url.Parse(targetHost)
if err != nil {
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(url)
var DefaultTransport http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConnsPerHost: 20,
}
proxy.Transport=DefaultTransport
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
modifyRequest(req)
}
proxy.ModifyResponse = modifyResponse()
proxy.ErrorHandler = errorHandler()
return proxy, nil
}
// modifyRequest 修改请求
func modifyRequest(req *http.Request) {
req.Header.Set("X-Proxy", "Maple-Reverse-Proxy")
}
// errorHandler 错误处理器
func errorHandler() func(http.ResponseWriter, *http.Request, error) {
return func(w http.ResponseWriter, req *http.Request, err error) {
fmt.Printf("Got error while modifying response: %v \n", err)
w.WriteHeader(500)
w.Write([]byte("proxy error"))
return
}
}
// mkdir 创建文件夹
func mkdir(dir string) {
exist, err := PathExists(dir)
if err != nil {
fmt.Println(err.Error())
} else {
if exist {
//fmt.Println(dir + "文件夹已存在!")
} else {
// 文件夹名称,权限
err := os.Mkdir(dir, os.ModePerm)
if err != nil {
fmt.Println("文件夹创建失败", err.Error())
} else {
fmt.Println("创建文件夹", dir)
}
}
}
}
// PathExists 判断文件夹是否存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// responseModify 修改http响应数据
func responseModify(r *http.Response) error {
//获取返回请求的内容类型
typeStr:=r.Header.Get("Content-Type")
//如果为图片
if strings.Contains(typeStr, "image"){
if r.StatusCode != 200 {
logger.Fatal("下载文件失败,http状态码为:",r.StatusCode,"响应内容为:",r.Body)
return nil
}
//缓存文件
querys:=r.Request.URL.Query()
args:=querys.Get("x")+querys.Get("y")+querys.Get("z")+querys.Get("ak")+querys.Get("styles")
js:=matchFile(md5V(args))
f, err := os.Create(js)
if err==nil{
res,err:=httputil.DumpResponse(r,true)
if err==nil{
f.Write(res)
if err1 := f.Close(); err == nil {
err = err1
}
}
}
}
//累计代理次数
proxyCount++
logger.Println("代理请求次数:",proxyCount)
if proxyCount>1000000{
proxyCount=0
logger.Println("代理超过:1000000 重置为0")
}
//logger.Println("完成请求:",r.Request.RequestURI)
return nil
}
// modifyResponse 修改http响应数据
func modifyResponse() func(*http.Response) error {
return func(r *http.Response)error {
return responseModify(r)
}
}
// matchFile 匹配缓存文件夹
func matchFile(bs string) string{
dir:=bs[0:2]
mkdir(path.Join("cache-basedir",dir))
js := path.Join("cache-basedir",dir,bs)
return js
}
// md5V 生产M5D值
func md5V(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
// isExist 判断文件是否否存在
func isExist(path string)(bool){
_, err := os.Stat(path)
if err != nil{
if os.IsExist(err){
return true
}
if os.IsNotExist(err){
return false
}
fmt.Println(err)
return false
}
return true
}
// getLocalIps 获取本地IP
func getLocalIps()[]string{
addrs, err := net.InterfaceAddrs()
if err != nil {
fmt.Println(err)
logger.Println("无法获取本机ip信息",err)
//os.Exit(1)
}
var ips []string
for _, address := range addrs {
// 检查ip地址判断是否回环地址
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ips=append(ips,ipnet.IP.String())
}
}
}
return ips
}
// checkCacheFile 检查是否存在缓存文件
func checkCacheFile(r *http.Request) (string,error){
// 判断是否存在
querys:=r.URL.Query()
args:=querys.Get("x")+querys.Get("y")+querys.Get("z")+querys.Get("ak")+querys.Get("styles")
js:=matchFile(md5V(args))
//检查缓存文件
if isExist(js){
f,err:=os.Open(js)
defer f.Close()
fi,_:=f.Stat()
// 文件过小不缓存
if err==nil &&fi.Size() > 10240{
// 缓存文件
return js,nil
}
}
return js,errors.New("未找到缓存")
}
// proxyRequestHandler 具体处理请求
func proxyRequestHandler(w http.ResponseWriter, r *http.Request) {
host:=r.Host
requestLocal:=false
//检查是否本地IP
for _,localIP:=range localIPs{
if host==localIP{
requestLocal=true
break
}
}
// 处理本地IP情况
if requestLocal {
w.WriteHeader(400)
w.Write([]byte("错误的请求,请检查"))
return
}
//检查缓存文件是否存在
f,err:=checkCacheFile(r)
if err!=nil{
// 反向代理
executeProxy(w,r)
}else{
//使用缓存
executeCache(f,w,r)
}
}
// executeProxy 执行代理
func executeProxy(w http.ResponseWriter, r *http.Request){
//判断请求的协议
scheme := "http://"
if r.TLS != nil {
scheme = "https://"
}
// 从代理缓存池中 抓取请求代理
serverProxy := domain[host]
//如果未获取到
if serverProxy==nil{
//构建反向代理
proxy,err:=NewProxy(scheme+host)
//如果创建成功则放入缓存 TODO 多线程情况
if err==nil{
domain[host]=proxy
serverProxy=proxy
}else{
w.WriteHeader(400)
w.Write([]byte("错误的请求,请检查"))
return
}
}
serverProxy.ServeHTTP(w, r)
}
// executeCache 执行缓存
func executeCache(f string,w http.ResponseWriter, r *http.Request){
file,_:=os.Open(f)
buff,err2:=ioutil.ReadAll(file)
if err2==nil{
temr := bufio.NewReader(bytes.NewReader(buff))
resp, err3 := http.ReadResponse(temr, nil)
if err3 == nil{
for k,vv:=range resp.Header{
for _,v:=range vv{
w.Header().Add(k,v)
}
}
bufio.NewReader(resp.Body).WriteTo(w)
cacheCount++
logger.Println("缓存击中一次",f,"缓存击中共计次数:",cacheCount)
if cacheCount>1000000{
cacheCount=0
logger.Println("缓存击中超过:1000000 重置为0")
}
}else{
w.WriteHeader(400)
w.Write([]byte(err3.Error()))
}
}else{
// 使用缓存
executeProxy(w,r)
}
}
// mkFile 创建文件
func mkFile(file string) (*os.File, error) {
exist, err := PathExists(file)
if err != nil {
fmt.Println(err.Error())
return nil, err
} else {
if exist {
//fmt.Println(dir + "文件夹已存在!")
return os.OpenFile(file, os.O_RDWR|os.O_APPEND, 0600)
} else {
// 文件夹名称,权限
file, err := os.Create(file)
if err != nil {
logger.Println("文件夹创建失败", err.Error())
}
return file, nil
}
}
return nil, nil
}
func main() {
//创建日志文件
file, err := mkFile("httpProxy.log")
if err != nil {
log.Fatalln("fail to create budget.log file!")
}
logger = log.New(file, "", log.Llongfile)
logger.SetFlags(log.LstdFlags)
// 初始化代理计数
proxyCount=0
flag.IntVar(&port, "P", 80, "端口号,默认为80")
flag.StringVar(&host, "H", "0.0.0.0", "主机地址,默认为0.0.0.0")
flag.StringVar(&internetIP, "I", "localhost", "主机公网地址,默认为")
flag.Parse()
// 获取本地IP
localIPs=getLocalIps()
localIPs=append(localIPs,internetIP)
// 创建反向代理缓存
domain=make(map[string]*httputil.ReverseProxy)
// 创建http 服务
http.HandleFunc("/", proxyRequestHandler)
hostStr:=host+":"+strconv.Itoa(port)
logger.Println("启动host",hostStr)
// 启动http 服务
logger.Fatal(http.ListenAndServe(hostStr, nil))
}