用Go写的HTTP代理服务器

标题是《用Go写http代理服务器》但实际上更接近于用Go架设http代理服务器,因为代码实在太少了,就像在配置一样。

做这个http代理的起因是前段时间运维上遇到的一个问题:有一个内部网站架设在两台web服务器上,暂且叫机器A和机器B,DNS分别指向这两台服务器,两台服务器之间用HaProxy做软负载均衡,两个机器上的文件是自动同步的,数据库用的是同一个。访问这个网站的域名时,请求有时会分配到机器A有时候会分配到机器B。但是网站之前的设计没有考虑到这样的部署结构,于是访问机器A和访问机器B时会出现一些缓存数据重复覆盖之类的问题。

思来想去,之所以要配这样其实有两个目的,最主要的目的是双机备份,防止单点失败,间接好处才是负载均衡。并且这个内部网站负载并不高,所以负载均衡其实是可以牺牲的,进而想能不能把HaProxy配置为不管访问机器A还是机器B,只要机器A是存活的,就访问到机器A。负载运维的同事森林帮忙研究了HaProxy的配置,没有找到这样配置的办法。于是想说能不能做一个简单的http代理服务器,用Erlang应该很容易实现,之前做过一个Socket代理,没多少代码就实现了。

但实际用erlang实现起来,发现挺复杂,虽然erlang的Socket支持{packet, http}这样的设置参数,但是代理转发数据却总是遇到问题。后来想起Gol也有http包,于是到官方文档翻看了一遍,找到一个“ReverseProxy”类型,几行代码就可以架起一个http代理服务器(下面附第一次实验的代码),但是这个代理服务器有两个问题:其一是这个代理服务器不会重新设置请求的原始地址,导致代理请求以虚拟主机方式配置的网站时出错或无法代理。其二是不会复制返回的Cookie,代理请求成功了,但是网站却登录不了。这两点我在修改了ReverseProxy的代码实验成功后,提交到了Go的BUG列表里,第二点他们已经修复,第一点,他们给的反馈是没办法重置原始地址,因为作为一个反向代理,需要让服务器知道来源地址,BUG单地址

第一次实验失败的代码,实际上等于一个不支持Cookie的反向代理,获取新版Go应该就支持Cookie了,代码够少的:

package main 
import (    "os" 
    "log" 
    "http" ) 
func main() {
    targetUrl, err := http.ParseURL("http://www.baidu.com") 
    if err != nil {        panic("bad url")
    }
 
    proxy := http.NewSingleHostReverseProxy(targetUrl)
 
    http.Handle("/", proxy)
 
    log.Println("Start serving on port 1234")
 
    http.ListenAndServe(":1234", nil)
 
    os.Exit(0)
}

用上面这个代码代理请求google是可以的,但是请求baidu就会出错,因为来源URL的原因。

下面是我复制ReverseProxy的代码修改后的结果,实测过可以正常代理和登录网站:

package main 
import (    "os" 
    "io" 
    "log" 
    "http" 
    "strings" ) 
var targetURL *http.URL 
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 handler(w http.ResponseWriter, r *http.Request) {
    o := new(http.Request)
 
    *o = *r
 
    o.Host       = targetURL.Host
    o.URL.Scheme = targetURL.Scheme
    o.URL.Host   = targetURL.Host
    o.URL.Path   = singleJoiningSlash(targetURL.Path, o.URL.Path) 
    if q := o.URL.RawQuery; q != "" {
        o.URL.RawPath = o.URL.Path + "?" + q
    } else {
        o.URL.RawPath = o.URL.Path
    }
 
    o.URL.RawQuery = targetURL.RawQuery
 
    o.Proto      = "HTTP/1.1" 
    o.ProtoMajor = 1 
    o.ProtoMinor = 1 
    o.Close      = false 
 
    transport := http.DefaultTransport
 
    res, err := transport.RoundTrip(o) 
    if err != nil {
        log.Printf("http: proxy error: %v", err)
        w.WriteHeader(http.StatusInternalServerError)        return 
    }
 
    hdr := w.Header() 
    for k, vv := range res.Header {        for _, v := range vv {
            hdr.Add(k, v)
        }
    } 
    for _, c := range res.SetCookie {
        w.Header().Add("Set-Cookie", c.Raw)
    }
 
    w.WriteHeader(res.StatusCode) 
    if res.Body != nil {
        io.Copy(w, res.Body)
    }
} 
func main() {
    url, err := http.ParseURL("http://www.baidu.com") 
    if err != nil {
        log.Println("Bad target URL")
    }
 
    targetURL = url
 
    http.HandleFunc("/", handler)
 
    log.Println("Start serving on port 1234")
 
    http.ListenAndServe(":1234", nil)
 
    os.Exit(0)
}

我觉得Go可以把内置的代理模块声明为HttpProxy然后通过设置Proxy的实例是ReverseProxy还是OutGoingProxy来决定要不要修改请求的来源地址。

当这个http代理服务器代码初步实现的时候,运维上的那个需求已经没有了。。。于是就没有继续把这个http代理实现下去,就当作一次练习吧 :)

做完这个程序我的感受是:接触Go的时间并不长,没有像erlang那样实际用于项目。但是Go却给我以前做.net开发时候的感觉,.net虽然是闭源的,但是通过Reflector可以很容易的看到内部机制的设计和实现,让你在开发的时候可以更确定自己在做什么,平台又会为你做什么,甚至可以做一些Hack。相较于erlang,Go让我觉得更容易触摸到它的内部,通过阅读系统包的代码你可以知道它的Socket包是怎么实现的,erlang也是开源项目,我也曾尝试深入阅读底层的代码,但是总是没找到那种感觉。我想这跟Go的项目结构和文档组织方式有很大关系吧。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2017-01-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏社区的朋友们

企鹅社区移动版Vue2.0升级手记

引入Vue框架比较早,随着2.0的升级,受到业界的高度关注,应用也越来越广泛,所以我们也得跟上步伐。企鹅社区移动版前端采用VUE 1.0开发。随着官方2.0的推...

2.9K00
来自专栏非著名程序员

Android Studio详细安装流程和配置、主题

? 原文作者:欧神. 杨 / OCN Yang 原文地址:http://ocnyang.com/2016/09/13/AndroidStudioSet/ 特别...

29660
来自专栏葡萄城控件技术团队

SoapUI实践:自动化测试、压力测试、持续集成

因为项目的原因,前段时间研究并使用了 SoapUI 测试工具进行自测开发的 api。下面将研究的成果展示给大家,希望对需要的人有所帮助。 SoapUI 是什么?...

46130
来自专栏互联网杂技

当你在浏览器中输入Google.com并且按下回车之后发生了什么?

回车键按下 为了从头开始,我们选择键盘上的回车键被按到最低处作为起点。在这个时刻,一个专用于回车键的电流回路被直接或者通过电容器闭合了,使得少量的电流进入了键盘...

380130
来自专栏应用案例

Web前端面试题小集

来自:前端打小怪升级笔记,作者:spademan segmentfault.com/a/1190000008322096 一、一个页面上两个div左右铺满整个浏...

36590
来自专栏Danny的专栏

关于VB6.0中控件加载的难题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

26140
来自专栏搜云库

在 Linux 上搭建Jekyll静态博客

在CentOS,Ubuntu 按照同样步骤安装,Ruby Gems 往往都无法搭建成,每次都是依赖不对,各种奇葩原因,解决办法就是使用 RVM 安装,解决 Ru...

49180
来自专栏FreeBuf

Java反序列化漏洞:在受限环境中从漏洞发现到获取反向Shell

Java反序列化漏洞可以说是Java安全的一块心病,近年来更是在安全界“出尽风头”。其实说到Java反序列化的问题,早在2015年年初的在AppSecCali大...

15420
来自专栏社区的朋友们

爬虫实战 : 爬虫之 web 自动化终极杀手(下)

最近写了好几个简单的爬虫,踩了好几个深坑,在这里总结一下,给大家在编写爬虫时候能给点思路。本次爬虫内容有:静态页面的爬取。动态页面的爬取。web 自动化终极爬虫...

1.9K10
来自专栏腾讯云安全的专栏

[经验分享]——XSS 入门介绍

27130

扫码关注云+社区

领取腾讯云代金券