

信号监听方式启动两个web服务,分别是9091 9092 分别返回 web1 web2
webmain.go
type web1handler struct {}
func(web1handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("web1"))
}
type web2handler struct {}
func(web2handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("web2"))
}
func main() {
c:=make(chan os.Signal)
go(func() {
http.ListenAndServe(":9091",web1handler{})
})()
go(func() {
http.ListenAndServe(":9092",web2handler{})
})()
signal.Notify(c,os.Interrupt)
s:=<-c
log.Println(s)
}Httpclient 初步使用(转发)
myproxy.go
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
type ProxyHandler struct {}
func(* ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//fmt.Println(r.RequestURI)
// /a?b=123
defer func() {
if err := recover();err != nil{
w.WriteHeader(500)
log.Println(err)
}
}()
fmt.Println(r.URL) // /a?b=123
fmt.Println(r.URL.Path) // /a
if r.URL.Path == "/a"{
newreq,_ := http.NewRequest(r.Method,"http://localhost:9091",r.Body)
newresponse,_ := http.DefaultClient.Do(newreq)
res_cont,_ := ioutil.ReadAll(newresponse.Body)
w.Write(res_cont)
return
}
w.Write([]byte("default index"))
}
func main() {
http.ListenAndServe(":8080",&ProxyHandler{})
} 

在httpserver中实现Basic Auth的认证和解析


GO实现
1、在头 里设置WWW-Authenticate
2、返回401 writer.Header().Set("WWW-Authenticate", `Basic realm="您必须输入用户名和密码"`)
writer.WriteHeader(http.StatusUnauthorized) return
客户端应答
1、假设用户名和密码是 sunlong和123
2、那么把两者拼接成 sunlong:123
3、然后base64编码 变成 c2hlbnlpOjEyMw==
4、发送请求时 添加头 Authorization: Basic c2hlbnlpOjEyMw==
服务器判断
auth:=request.Header.Get("Authorization")
if auth==""{
writer.Header().Set("WWW-Authenticate", `Basic realm="您必须输入用户名和密码"`)
writer.WriteHeader(http.StatusUnauthorized)
return
}让”反向代理”支持Basic Auth验证框弹出
拷贝头
import "net/http"
func CloneHeader(dest *http.Header,src http.Header) {
for k, vv := range src {
dest.Set(k,vv[0])
}
}
然后主函数里加入
h:=w.Header()CloneHeader(&h,newresponse.Header)会发现还是没用。。。。。
看下响应头

w.WriteHeader(newresponse.StatusCode)
一句话搞定,有木有~~~
完整代码
package main
import (
"encoding/base64"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
)
type web1handler struct {
}
func(web1handler) ServeHTTP(writer http.ResponseWriter,request *http.Request){
auth := request.Header.Get("Authorization")
fmt.Println(auth)
if auth == ""{
writer.Header().Set("WWW-Authenticate",`Basic realm="您必须输入用户名和密码"`)
writer.WriteHeader(http.StatusUnauthorized)
return
}
//fmt.Println(auth)
// Basic c3VubG9uZzoxMjM=
auth_list := strings.Split(auth," ")
if len(auth_list) == 2 && auth_list[0] == "Basic"{
res,err := base64.StdEncoding.DecodeString(auth_list[1])
if err == nil && string(res) == "sunlong:123456" {
writer.Write([]byte("<h1>web1</h1>"))
return
}
}
writer.Write([]byte("用户名密码错误"))
}
type web2handler struct {}
func(web2handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("<h1>web2</h1>"))
}
func main(){
c := make(chan os.Signal)
go(func() {
http.ListenAndServe(":9091",web1handler{})
})()
go(func() {
http.ListenAndServe(":9092",web2handler{})
})()
signal.Notify(c,os.Interrupt)
s := <- c
log.Println(s)
}package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"go-networks/util"
)
type ProxyHandler struct {}
func(* ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//fmt.Println(r.RequestURI)
// /a?b=123
defer func() {
if err := recover();err != nil{
w.WriteHeader(500)
log.Println(err)
}
}()
fmt.Println(r.URL) // /a?b=123
fmt.Println(r.URL.Path) // /a
if r.URL.Path == "/a"{
newreq,_ := http.NewRequest(r.Method,"http://localhost:9091",r.Body)
util.CloneHeader(r.Header,&newreq.Header)
newresponse,_ := http.DefaultClient.Do(newreq)
getHeader := w.Header()
util.CloneHeader(newresponse.Header,&getHeader)
defer newresponse.Body.Close()
w.WriteHeader(newresponse.StatusCode)
res_cont,_ := ioutil.ReadAll(newresponse.Body)
w.Write(res_cont)
return
}
w.Write([]byte("default index"))
}
func main() {
http.ListenAndServe(":8080",&ProxyHandler{})
}package util
import "net/http"
func CloneHeader(src http.Header,dest *http.Header) {
for k,v:=range src{
dest.Set(k,v[0])
}
}
怎么获取真实IP?
writer.Write([]byte(fmt.Sprintf("<h1>web1,来自于:%s</h1>", request.RemoteAddr))) (可以用net.SplitHostPort()进行分割)
很不幸的是 得到是 代理服务器IP

X-Forwarded-For
X-Forwarded-For 是一个 HTTP 扩展 HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。
写个获取IP方法
func(web1handler) GetIP(request *http.Request) string{
ips:=request.Header.Get("x-forwarded-for")
if ips!=""{
ips_list:= strings.Split(ips, ",")
if len(ips_list)>0 && ips_list[0]!=""{
return ips_list[0]
}
}
return request.RemoteAddr
}
以上代码来自Beego改编设计ini配置文件格式、配置”反向代理”路径映射
package util
import (
"github.com/go-ini/ini"
"log"
"os"
)
var ProxyConfigs map[string]string
type EnvConfig *os.File
func init(){
ProxyConfigs = make(map[string]string)
EnvConfig,err:= ini.Load("env")
if err!=nil{
log.Println(err)
return
}
proxy,_:=EnvConfig.GetSection("proxy") //假设是固定的 分区
if proxy!=nil{
secs:=proxy.ChildSections() //获取子分区
for _,sec := range secs{
path,_ := sec.GetKey("path")
pass,_:= sec.GetKey("pass")//固定Key
if path!=nil && pass !=nil{
ProxyConfigs[path.Value()]=pass.Value()
}
}
}
}package util
import (
"io/ioutil"
"net/http"
)
func CloneHeader(src http.Header,dest *http.Header) {
for k,v:=range src{
dest.Set(k,v[0])
}
}
func RequestUrl(w http.ResponseWriter, r *http.Request,url string) {
newreq,_:=http.NewRequest(r.Method,url,r.Body)
CloneHeader(r.Header,&newreq.Header)
newreq.Header.Add("x-forwarded-for",r.RemoteAddr)
newresponse,_:=http.DefaultClient.Do(newreq)
getHeader:=w.Header()
CloneHeader(newresponse.Header,&getHeader) //拷贝响应头 给客户端
w.WriteHeader(newresponse.StatusCode) // 写入http status
defer newresponse.Body.Close()
res_cont,_:=ioutil.ReadAll(newresponse.Body)
w.Write(res_cont) // 写入响应给客户端
}[proxy] [proxy.a] path=/web1 pass=http://127.0.0.1:9091 [proxy.b] path=/web2 pass=http://127.0.0.1:9092
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
. "go-networks/util"
"regexp"
)
type ProxyHandler struct {}
func(* ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//fmt.Println(r.RequestURI)
// /a?b=123
defer func() {
if err := recover();err != nil{
w.WriteHeader(500)
log.Println(err)
}
}()
fmt.Println(r.URL) // /a?b=123
fmt.Println(r.URL.Path) // /a
for k,v := range ProxyConfigs{
fmt.Println(k)
fmt.Println(v)
if matched,_:= regexp.MatchString(k,r.URL.Path);matched == true{
RequestUrl(w,r,v)
return
}
}
if r.URL.Path == "/a"{
newreq,_ := http.NewRequest(r.Method,"http://localhost:9091",r.Body)
CloneHeader(r.Header,&newreq.Header)
newreq.Header.Set("x-forwarded-for",r.RemoteAddr)
newresponse,_ := http.DefaultClient.Do(newreq)
getHeader := w.Header()
CloneHeader(newresponse.Header,&getHeader)//拷贝响应头给客户端
defer newresponse.Body.Close()
w.WriteHeader(newresponse.StatusCode)// 写入http status
res_cont,_ := ioutil.ReadAll(newresponse.Body)
w.Write(res_cont)
return
}
w.Write([]byte("default index"))
}
func main() {
http.ListenAndServe(":8080",&ProxyHandler{})
}package main
import (
"encoding/base64"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
)
type web1handler struct {
}
func (web1handler) GetIp(request *http.Request) string{
ips := request.Header.Get("x-forwarded-for")
if ips != ""{
ips_list := strings.Split(ips,",")
if len(ips_list) > 0 && ips_list[0] != ""{
return ips_list[0]
}
}
return request.RemoteAddr
}
func(this web1handler) ServeHTTP(writer http.ResponseWriter,request *http.Request){
auth := request.Header.Get("Authorization")
fmt.Println(auth)
if auth == ""{
writer.Header().Set("WWW-Authenticate",`Basic realm="您必须输入用户名和密码"`)
writer.WriteHeader(http.StatusUnauthorized)
return
}
//fmt.Println(auth)
// Basic c3VubG9uZzoxMjM=
auth_list := strings.Split(auth," ")
if len(auth_list) == 2 && auth_list[0] == "Basic"{
res,err := base64.StdEncoding.DecodeString(auth_list[1])
if err == nil && string(res) == "sunlong:123456" {
writer.Write([]byte(fmt.Sprintf("<h1>web1,来自:%s</h1>",this.GetIp(request))))
return
}
}
writer.Write([]byte("用户名密码错误"))
}
type web2handler struct {}
func(web2handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("<h1>web2</h1>"))
}
func main(){
c := make(chan os.Signal)
go(func() {
http.ListenAndServe(":9091",web1handler{})
})()
go(func() {
http.ListenAndServe(":9092",web2handler{})
})()
signal.Notify(c,os.Interrupt)
s := <- c
log.Println(s)
}使用Transport来进行反代请求、go内置的反向代理函数
实现的方式是用了

Transport
最终真正产生响应结果的是 transport的 RoundTrip方法(大约在client.go ,252行),而httpclient 已经帮我们把譬如cookie、重定向、timeout等http请求的整个过程(事务)机制都包含了且有默认值
DefaultTransport

http.DefaultTransport.RoundTrip(newreq)
利用Transport实现反向代理
func RequestUrl(w http.ResponseWriter, r *http.Request,url string) {
newreq,_:=http.NewRequest(r.Method,url,r.Body)
CloneHeader(r.Header,&newreq.Header)
newreq.Header.Add("x-forwarded-for",r.RemoteAddr)
dt := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 3*time.Second,
}
newresponse,err := dt.RoundTrip(newreq)
if err != nil{
log.Println(err)
return
}
//newresponse,_:=http.DefaultClient.Do(newreq)
getHeader:=w.Header()
CloneHeader(newresponse.Header,&getHeader) //拷贝响应头 给客户端
w.WriteHeader(newresponse.StatusCode) // 写入http status
defer newresponse.Body.Close()
res_cont,_:=ioutil.ReadAll(newresponse.Body)
w.Write(res_cont) // 写入响应给客户端
}最后介绍go内置的反代函数
上面写了那么多代码,只是为了了解反向代理内部实现机制,其实go也有内置的反向代理函数,三句话搞定
target,_:=url.Parse(v) proxy:=httputil.NewSingleHostReverseProxy(target) proxy.ServeHTTP(w,r)
下面是官方内置反向代理源码,有兴趣可以继续分许
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
ctx := req.Context()
if cn, ok := rw.(http.CloseNotifier); ok {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
notifyChan := cn.CloseNotify()
go func() {
select {
case <-notifyChan:
cancel()
case <-ctx.Done():
}
}()
}
outreq := req.WithContext(ctx) // includes shallow copies of maps, but okay
if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
}
outreq.Header = cloneHeader(req.Header)
p.Director(outreq)
outreq.Close = false
reqUpType := upgradeType(outreq.Header)
removeConnectionHeaders(outreq.Header)
// Remove hop-by-hop headers to the backend. Especially
// important is "Connection" because we want a persistent
// connection, regardless of what the client sent to us.
for _, h := range hopHeaders {
hv := outreq.Header.Get(h)
if hv == "" {
continue
}
if h == "Te" && hv == "trailers" {
// Issue 21096: tell backend applications that
// care about trailer support that we support
// trailers. (We do, but we don't go out of
// our way to advertise that unless the
// incoming client request thought it was
// worth mentioning)
continue
}
outreq.Header.Del(h)
}
// After stripping all the hop-by-hop connection headers above, add back any
// necessary for protocol upgrades, such as for websockets.
if reqUpType != "" {
outreq.Header.Set("Connection", "Upgrade")
outreq.Header.Set("Upgrade", reqUpType)
}
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := outreq.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
outreq.Header.Set("X-Forwarded-For", clientIP)
}
res, err := transport.RoundTrip(outreq)
if err != nil {
p.getErrorHandler()(rw, outreq, err)
return
}
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
if res.StatusCode == http.StatusSwitchingProtocols {
if !p.modifyResponse(rw, res, outreq) {
return
}
p.handleUpgradeResponse(rw, outreq, res)
return
}
removeConnectionHeaders(res.Header)
for _, h := range hopHeaders {
res.Header.Del(h)
}
if !p.modifyResponse(rw, res, outreq) {
return
}
copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response,
// at least for *http.Transport. Build it up from Trailer.
announcedTrailers := len(res.Trailer)
if announcedTrailers > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
rw.WriteHeader(res.StatusCode)
err = p.copyResponse(rw, res.Body, p.flushInterval(req, res))
if err != nil {
defer res.Body.Close()
// Since we're streaming the response, if we run into an error all we can do
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
// on read error while copying body.
if !shouldPanicOnCopyError(req) {
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
return
}
panic(http.ErrAbortHandler)
}
res.Body.Close() // close now, instead of defer, to populate res.Trailer
if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short
// bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
if len(res.Trailer) == announcedTrailers {
copyHeader(rw.Header(), res.Trailer)
return
}
for k, vv := range res.Trailer {
k = http.TrailerPrefix + k
for _, v := range vv {
rw.Header().Add(k, v)
}
}
}最终代码:
下面继续会学习go的负载均衡算法和限流相关知识
- 最简单的随机算法实现负载均衡
- 负载均衡算法之ip_hash
- 负载均衡算法之加权随机算法
- 负载均衡算法之轮询算法
- 负载均衡算法之加权轮询算法
- 负载均衡算法之平滑加权轮询算法
- 简易健康检查:http服务定时检查
- 简易FailOver机制: 普通轮询算法下的计数器机制
- 普通加权轮询算法下的降权机制
- 平滑加权轮询算法下的降权机制
- 限流(令牌桶算法),熔断机制,
- 微服务框架学习 go-kit和 go-micro的学习(学习这么多最终还是会使用官方的微服务框架,学习之前请先熟悉grpc和服务注册,服务发现相关知识)
路漫漫其修远兮吾将上下而求所