函数是一等公民
// 新建函数类型
type HandlerFunc func(http.ResponseWriter, *http.Request)// 新建函数方法
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {f(w, r)
}// 主函数
func main() {// 实例化函数hf := HandlerFunc(HelloHandler)// 创建response对象resp := httptest.NewRecorder()// 实例化request对象req := httptest.NewRequest("GET", "/", bytes.NewBuffer([]byte("test")))// 在response中写入"hello world"hf.ServeHTTP(resp, req)// 读取response中的内容bts, _ := ioutil.ReadAll(resp.Body)// 打印response中的内容fmt.Println(string(bts))
}// 创建具体的handler函数
func HelloHandler(res http.ResponseWriter, req *http.Request) {res.Write([]byte("Hello world"))
}
http服务端
var (Addr = ":1210"
)func main() {// 创建路由器mux := http.NewServeMux()// 设置路由规则mux.HandleFunc("/bye", sayBye)// 创建服务器server := &http.Server{Addr: Addr,WriteTimeout: time.Second * 3,Handler: mux,}// 监听端口并提供服务log.Println("Starting httpserver at " + Addr)log.Fatal(server.ListenAndServe())
}// 新建具体的handler函数
func sayBye(w http.ResponseWriter, r *http.Request) {time.Sleep(1 * time.Second)w.Write([]byte("bye bye ,this is httpServer"))
}
http客户端
func main() {// 创建连接池transport := &http.Transport{DialContext: (&net.Dialer{Timeout: 30 * time.Second, //连接超时KeepAlive: 30 * time.Second, //探活时间}).DialContext,MaxIdleConns: 100, //最大空闲连接IdleConnTimeout: 90 * time.Second, //空闲超时时间TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间ExpectContinueTimeout: 1 * time.Second, //100-continue状态码超时时间}// 创建客户端client := &http.Client{Timeout: time.Second * 30, //请求超时时间Transport: transport, //连接池设置}// 请求数据resp, err := client.Get("http://127.0.0.1:1210/bye")if err != nil {panic(err)}defer resp.Body.Close()// 读取内容bds, err := ioutil.ReadAll(resp.Body)if err != nil {panic(err)}// 打印返回的内容fmt.Println(string(bds))
}
web浏览器代理
// web浏览器代理type Pxy struct{}func (p *Pxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {// 客户端的ip和端口fmt.Printf("Received request %s %s %s\n", req.Method, req.Host, req.RemoteAddr)// 创建连接池transport := http.DefaultTransport// step 1,浅拷贝对象,然后就再新增属性数据outReq := new(http.Request)*outReq = *req// 获取ip和端口号if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {if prior, ok := outReq.Header["X-Forwarded-For"]; ok {clientIP = strings.Join(prior, ", ") + ", " + clientIP //增加ip}// 设置请求头outReq.Header.Set("X-Forwarded-For", clientIP)}// step 2, 请求下游res, err := transport.RoundTrip(outReq)if err != nil {rw.WriteHeader(http.StatusBadGateway)return}// step 3, 把下游请求内容返回给上游for key, value := range res.Header {for _, v := range value {rw.Header().Add(key, v)}}rw.WriteHeader(res.StatusCode)io.Copy(rw, res.Body)res.Body.Close()
}func main() {fmt.Println("Serve on :8080")http.Handle("/", &Pxy{})http.ListenAndServe("0.0.0.0:8080", nil)
}
反向代理
建立(反向代理)的下游web服务端
type RealServer struct {Addr string
}// hello handler
func (r *RealServer) HelloHandler(w http.ResponseWriter, req *http.Request) {//127.0.0.1:8008/abc?sdsdsa=11//r.Addr=127.0.0.1:8008//req.URL.Path=/abc//fmt.Println(req.Host)upath := fmt.Sprintf("http://%s%s\n", r.Addr, req.URL.Path)realIP := fmt.Sprintf("RemoteAddr=%s,X-Forwarded-For=%v,X-Real-Ip=%v\n", req.RemoteAddr, req.Header.Get("X-Forwarded-For"), req.Header.Get("X-Real-Ip"))header := fmt.Sprintf("headers =%v\n", req.Header)io.WriteString(w, upath)io.WriteString(w, realIP)io.WriteString(w, header)}// error handler
func (r *RealServer) ErrorHandler(w http.ResponseWriter, req *http.Request) {upath := "error handler"w.WriteHeader(500)io.WriteString(w, upath)
}// timeout handler
func (r *RealServer) TimeoutHandler(w http.ResponseWriter, req *http.Request) {time.Sleep(6 * time.Second)upath := "timeout handler"w.WriteHeader(200)io.WriteString(w, upath)
}func (r *RealServer) Run() {// 打印web服务端ip地址log.Println("Starting httpserver at " + r.Addr)// 注册路由mux := http.NewServeMux()// 绑定path和逻辑处理函数mux.HandleFunc("/", r.HelloHandler)mux.HandleFunc("/base/error", r.ErrorHandler)mux.HandleFunc("/test_http_string/test_http_string/aaa", r.TimeoutHandler)// 设置服务相关参数server := &http.Server{Addr: r.Addr, // ip和端口地址WriteTimeout: time.Second * 3, // 设置超时时间Handler: mux, //绑定路由}// 另起一个协程监听端口消息go func() {log.Fatal(server.ListenAndServe())}()
}func main() {rs1 := &RealServer{Addr: "127.0.0.1:2003"}rs1.Run()rs2 := &RealServer{Addr: "127.0.0.1:2004"}rs2.Run()//监听关闭信号quit := make(chan os.Signal)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<-quit
}
创建反向代理
var (proxy_addr = "http://127.0.0.1:2003"port = "2002"
)func handler(w http.ResponseWriter, r *http.Request) {//step 1 解析(客户端)代理地址,并更改请求体的协议和主机proxy, err := url.Parse(proxy_addr)r.URL.Scheme = proxy.Schemer.URL.Host = proxy.Host//step 2 请求下游(服务端)transport := http.DefaultTransportresp, err := transport.RoundTrip(r)if err != nil {log.Print(err)return}//step 3 把下游(服务端)请求内容返回给上游(客户端)for k, vv := range resp.Header {for _, v := range vv {w.Header().Add(k, v)}}defer resp.Body.Close()bufio.NewReader(resp.Body).WriteTo(w)
}func main() {// 绑定路由,监听客户端端口的信息http.HandleFunc("/", handler)log.Println("Start serving on port " + port)// 监听客户端端口传过来的信息err := http.ListenAndServe(":"+port, nil)if err != nil {log.Fatal(err)}
}
模拟客户端请求,请求客户端IP:端口/path
curl 'http://127.0.0.1:2003/sdadasds?sdsdsdsdsadadas=111'
基于ReverseProxy搭建http代理
- 更改内容支持
- 错误信息回调
- 支持自定义负载均衡
- url重写
- 连接池功能
- 支持websocket服务:独立章节介绍
- 支持https协议
ReverseProxy实现的简单的http代理
var addr = "127.0.0.1:2002"func main() {//127.0.0.1:2002/xxx//127.0.0.1:2003/base/xxxrs1 := "http://127.0.0.1:2003/base"url1, err1 := url.Parse(rs1)fmt.Printf("url1=%v\n", url1) //url1=http://127.0.0.1:2003/baseif err1 != nil {log.Println(err1)}// 实例化代理proxy := httputil.NewSingleHostReverseProxy(url1)log.Println("Starting httpserver at " + addr)// 开启监听,地址为addr = "127.0.0.1:2002"向地址url1=http://127.0.0.1:2003/base转发log.Fatal(http.ListenAndServe(addr, proxy))
}
ReverseProxy代理修改客户端请求的内容
// 客户端请求ip:端口号
var addr = "127.0.0.1:2002"// 拼接路径
func singleJoiningSlash(a, b string) string {aslash := strings.HasSuffix(a, "/")bslash := strings.HasPrefix(b, "/")switch {case aslash && bslash:return a + b[1:]case !aslash && !bslash:return a + "/" + b}return a + b
}// 实例化代理
func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {//客户端请求的url:http://127.0.0.1:2002/dir?name=123targetQuery := target.RawQuery //RayQuery: name=123// 请求转发函数director := func(req *http.Request) {//url_rewrite//127.0.0.1:2002/dir/abc ==> 127.0.0.1:2003/base/abc ??//127.0.0.1:2002/dir/abc ==> 127.0.0.1:2002/abc//127.0.0.1:2002/abc ==> 127.0.0.1:2003/base/abcre, _ := regexp.Compile("^/dir(.*)")req.URL.Path = re.ReplaceAllString(req.URL.Path, "$1")req.URL.Scheme = target.Scheme //Scheme: httpreq.URL.Host = target.Host //Host: 127.0.0.1:2002//拼接路径req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) //target.Path : /base ; req.URL.Path : /dirif targetQuery == "" || req.URL.RawQuery == "" {req.URL.RawQuery = targetQuery + req.URL.RawQuery} else {req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery}// 添加header头// User-Agent,浏览器的用户代理字符串。告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本// 例如: User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; InfoPath.2; .NET4.0E)if _, ok := req.Header["User-Agent"]; !ok {req.Header.Set("User-Agent", "")}}// 修改内容的函数modifyFunc := func(res *http.Response) error {// 当状态码异常的时候修改response.body里面的数据if res.StatusCode != 200 {return errors.New("error statusCode")oldPayload, err := ioutil.ReadAll(res.Body)if err != nil {return err}newPayLoad := []byte("hello " + string(oldPayload))res.Body = ioutil.NopCloser(bytes.NewBuffer(newPayLoad))res.ContentLength = int64(len(newPayLoad))res.Header.Set("Content-Length", fmt.Sprint(len(newPayLoad)))}return nil}// 打印错误日志的函数errorHandler := func(res http.ResponseWriter, req *http.Request, err error) {res.Write([]byte(err.Error()))}return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyFunc, ErrorHandler: errorHandler}
}func main() {//127.0.0.1:2002/xxx//127.0.0.1:2003/base/xxx// 代理转发的地址rs1 := "http://127.0.0.1:2003/base"// 解析urlurl1, err1 := url.Parse(rs1) //url1=http://127.0.0.1:2003/basefmt.Printf("url1=%v\n", url1)if err1 != nil {log.Println(err1)}// 创建proxy实例proxy := NewSingleHostReverseProxy(url1)log.Println("Starting httpserver at " + addr)log.Fatal(http.ListenAndServe(addr, proxy))
}
一些特殊的请求头参数
X-Forwarded-For
头信息可以有多个,中间用逗号分隔,第一项为真实的客户端IP,剩下的就是曾经经过的代理或负载均衡的IP地址,经过几个就会出现几个。但是由于X-Forwarded-For内容是可以修改的所以并不安全和可信,最好满足IP的格式才读取,这样安全性高点。标准格式如下所示:
X-Forwarded-For: client1, proxy1, proxy2
X-Real-IP
客户端请求的实际IP
Connection
有两个值:Close和Keep-Alive。当使用Connection:Close时,和HTTP1.0协议是一样的,当read方法读完数据时立即返回;而使用Connection:Keep-Alive时,read方法在读完数据后还要被阻塞一段时间。直接读取数据超时时间过后,还继续往下执行
Connection: closeTE
客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息
TE: trailers,deflate;q=0.5Trailer
响应头允许发送方包括为了提供可能,而所述消息体被发送,诸如消息完整性检查,数字签名,或后处理状态动态地生成的元数据在分块消息的末尾附加字段,比如超时时间
Trailer: ExpiresUser-Agent
是Http协议中的一部分,属于头域的组成部分,User Agent也简称UA。它是一个特殊字符串头,是一种向访问网站提供你所使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。通过这个标 识,用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计;例如用手机访问谷歌和电脑访问是不一样的,这些是谷歌根据访问者的 UA来判断的
User-Agent:Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50
End-to-end
端到端头部
此类头部字段会转发给 请求/响应 的最终接收目标,必须保存在由缓存生成的响应头部,必须被转发。
Hop-by-hop
逐跳首部
此类头部字段只对单次转发有效。会因为转发给缓存/代理服务器而失效,在HTTP 1.1 版本之后,如果要使用Hop-by-hop头部字段则需要提供Connection字段。而且除了以下8个字段为逐跳字段,其余均为端到端字段。
Connection、Keep-Alive、Proxy-Authenticate、Proxy-Authenrization、Trailer、TE、Tranfer-Encoding、Upgrade
创建服务端
type RealServer struct {Addr string
}// hello handler
func (r *RealServer) HelloHandler(w http.ResponseWriter, req *http.Request) {//127.0.0.1:8008/abc?sdsdsa=11//r.Addr=127.0.0.1:8008//req.URL.Path=/abc//fmt.Println(req.Host)upath := fmt.Sprintf("http://%s%s\n", r.Addr, req.URL.Path)realIP := fmt.Sprintf("RemoteAddr=%s,X-Forwarded-For=%v,X-Real-Ip=%v\n", req.RemoteAddr, req.Header.Get("X-Forwarded-For"), req.Header.Get("X-Real-Ip"))header := fmt.Sprintf("headers =%v\n", req.Header)io.WriteString(w, upath)io.WriteString(w, realIP)io.WriteString(w, header)}// error handler
func (r *RealServer) ErrorHandler(w http.ResponseWriter, req *http.Request) {upath := "error handler"w.WriteHeader(500)io.WriteString(w, upath)
}// timeout handler
func (r *RealServer) TimeoutHandler(w http.ResponseWriter, req *http.Request) {time.Sleep(6 * time.Second)upath := "timeout handler"w.WriteHeader(200)io.WriteString(w, upath)
}func (r *RealServer) Run() {// 打印web服务端ip地址log.Println("Starting httpserver at " + r.Addr)// 注册路由mux := http.NewServeMux()// 绑定path和逻辑处理函数mux.HandleFunc("/", r.HelloHandler)mux.HandleFunc("/base/error", r.ErrorHandler)mux.HandleFunc("/test_http_string/test_http_string/aaa", r.TimeoutHandler)// 设置服务相关参数server := &http.Server{Addr: r.Addr, // ip和端口地址WriteTimeout: time.Second * 3, // 设置超时时间Handler: mux, //绑定路由}// 另起一个协程监听端口消息go func() {log.Fatal(server.ListenAndServe())}()
}func main() {// 根据端口127.0.0.1:2003创建服务rs1 := &RealServer{Addr: "127.0.0.1:2003"}rs1.Run()// 根据端口127.0.0.1:2004创建服务rs2 := &RealServer{Addr: "127.0.0.1:2004"}rs2.Run()//监听关闭信号quit := make(chan os.Signal)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<-quit
}
创建第一级代理
// 客户端请求地址
var addr = "127.0.0.1:2001"// 设置连接池
var transport = &http.Transport{DialContext: (&net.Dialer{Timeout: 30 * time.Second, //连接超时KeepAlive: 30 * time.Second, //长连接超时时间}).DialContext,MaxIdleConns: 100, //最大空闲连接IdleConnTimeout: 90 * time.Second, //空闲超时时间TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间ExpectContinueTimeout: 1 * time.Second, //100-continue 超时时间
}// 字符串拼接
func singleJoiningSlash(a, b string) string {aslash := strings.HasSuffix(a, "/")bslash := strings.HasPrefix(b, "/")switch {case aslash && bslash:return a + b[1:]case !aslash && !bslash:return a + "/" + b}return a + b
}// 创建代理实例
func NewMultipleHostsReverseProxy(targets []*url.URL) *httputil.ReverseProxy {// 请求协调者director := func(req *http.Request) {// url_rewrite// 127.0.0.1:2002/dir/abc ==> 127.0.0.1:2003/base/abc ??// 127.0.0.1:2002/dir/abc ==> 127.0.0.1:2002/abc// 127.0.0.1:2002/abc ==> 127.0.0.1:2003/base/abcre, _ := regexp.Compile("^/dir(.*)")req.URL.Path = re.ReplaceAllString(req.URL.Path, "$1")// 随机负载均衡targetIndex := rand.Intn(len(targets))target := targets[targetIndex]targetQuery := target.RawQueryreq.URL.Scheme = target.Schemereq.URL.Host = target.Host// url地址重写:重写前:/aa 重写后:/base/aareq.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)if targetQuery == "" || req.URL.RawQuery == "" {req.URL.RawQuery = targetQuery + req.URL.RawQuery} else {req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery}if _, ok := req.Header["User-Agent"]; !ok {req.Header.Set("User-Agent", "user-agent")}// 只在第一代理中设置此header头req.Header.Set("X-Real-Ip", req.RemoteAddr)}// 更改内容modifyFunc := func(resp *http.Response) error {// 请求以下命令:curl 'http://127.0.0.1:2002/error'if resp.StatusCode != 200 {// 获取内容oldPayload, err := ioutil.ReadAll(resp.Body)if err != nil {return err}//追加内容newPayload := []byte("StatusCode error:" + string(oldPayload))resp.Body = ioutil.NopCloser(bytes.NewBuffer(newPayload))resp.ContentLength = int64(len(newPayload))resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(newPayload)), 10))}return nil}// 错误回调:关闭real_server时测试,错误回调errFunc := func(w http.ResponseWriter, r *http.Request, err error) {http.Error(w, "ErrorHandler error:"+err.Error(), 500)}return &httputil.ReverseProxy{Director: director,Transport: transport,ModifyResponse: modifyFunc,ErrorHandler: errFunc}
}// 主函数
func main() {// 本代理地址rs1 := "http://127.0.0.1:2002"url1, err1 := url.Parse(rs1) // 解析代理地址if err1 != nil {log.Println(err1)}urls := []*url.URL{url1} // 创建代理列表proxy := NewMultipleHostsReverseProxy(urls) // 创建代理实例log.Println("Starting httpserver at " + addr)log.Fatal(http.ListenAndServe(addr, proxy)) // 监听客户端信息
}
创建二级代理
// 第一级代理的地址
var addr = "127.0.0.1:2002"// 创建连接池
var transport = &http.Transport{DialContext: (&net.Dialer{Timeout: 30 * time.Second, //连接超时KeepAlive: 30 * time.Second, //长连接超时时间}).DialContext,MaxIdleConns: 100, //最大空闲连接IdleConnTimeout: 90 * time.Second, //空闲超时时间TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间ExpectContinueTimeout: 1 * time.Second, //100-continue 超时时间
}// 拼接字符串
func singleJoiningSlash(a, b string) string {aslash := strings.HasSuffix(a, "/")bslash := strings.HasPrefix(b, "/")switch {case aslash && bslash:return a + b[1:]case !aslash && !bslash:return a + "/" + b}return a + b
}// 创建第二级代理实例
func NewMultipleHostsReverseProxy(targets []*url.URL) *httputil.ReverseProxy {//请求协调者director := func(req *http.Request) {//url_rewrite//127.0.0.1:2002/dir/abc ==> 127.0.0.1:2003/base/abc ??//127.0.0.1:2002/dir/abc ==> 127.0.0.1:2002/abc//127.0.0.1:2002/abc ==> 127.0.0.1:2003/base/abcre, _ := regexp.Compile("^/dir(.*)") //正则匹配req.URL.Path = re.ReplaceAllString(req.URL.Path, "$1") //替换//随机负载均衡targetIndex := rand.Intn(len(targets)) //根据服务器的序列号产生随机整数target := targets[targetIndex] //选择随机选取的服务器targetQuery := target.RawQuery //获取查询语句req.URL.Scheme = target.Scheme //协议种类,Scheme: httpreq.URL.Host = target.Host //地址,Host: 127.0.0.1:2003//todo 部分章节补充1//todo 当对域名(非内网)反向代理时需要设置此项。当作后端反向代理时不需要req.Host = target.Host// url地址重写:重写前:/aa 重写后:/base/aareq.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)if targetQuery == "" || req.URL.RawQuery == "" {req.URL.RawQuery = targetQuery + req.URL.RawQuery} else {req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery}// 设置用户代理头if _, ok := req.Header["User-Agent"]; !ok {req.Header.Set("User-Agent", "user-agent")}//只在第一代理中设置此header头,在二级及其以后的代理中直接转发即可//req.Header.Set("X-Real-Ip", req.RemoteAddr)}//更改内容modifyFunc := func(resp *http.Response) error {//请求以下命令:curl 'http://127.0.0.1:2002/error'//todo 部分章节功能补充2//todo 兼容websocketif strings.Contains(resp.Header.Get("Connection"), "Upgrade") {return nil}var payload []bytevar readErr error//todo 部分章节功能补充3//todo 兼容gzip压缩if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") {gr, err := gzip.NewReader(resp.Body)if err != nil {return err}payload, readErr = ioutil.ReadAll(gr)resp.Header.Del("Content-Encoding")} else {payload, readErr = ioutil.ReadAll(resp.Body)}if readErr != nil {return readErr}//异常请求时设置StatusCodeif resp.StatusCode != 200 {payload = []byte("StatusCode error:" + string(payload))}//todo 部分章节功能补充4//todo 因为预读了数据所以内容重新回写resp.Body = ioutil.NopCloser(bytes.NewBuffer(payload))resp.ContentLength = int64(len(payload))resp.Header.Set("Content-Length", strconv.FormatInt(int64(len(payload)), 10))return nil}//错误回调 :关闭real_server时测试,错误回调errFunc := func(w http.ResponseWriter, r *http.Request, err error) {http.Error(w, "ErrorHandler error:"+err.Error(), 500)}return &httputil.ReverseProxy{Director: director,Transport: transport,ModifyResponse: modifyFunc,ErrorHandler: errFunc}
}// 启动服务
func main() {// 请求服务器的地址//rs1 := "http://www.baidu.com"rs1 := "http://127.0.0.1:2003"url1, err1 := url.Parse(rs1)if err1 != nil {log.Println(err1)}// 请求服务器的地址//rs2 := "http://www.baidu.com"rs2 := "http://127.0.0.1:2004"url2, err2 := url.Parse(rs2)if err2 != nil {log.Println(err2)}// 将服务器的两个地址写进代理的请求列表中urls := []*url.URL{url1, url2}proxy := NewMultipleHostsReverseProxy(urls) //创建代理实例log.Println("Starting httpserver at " + addr) //打印日志log.Fatal(http.ListenAndServe(addr, proxy)) //开启监听
}