两个月前,学长让我给他写一个代理服务器。摸了一个鱼,看了一个月别人的代码,调Bug又调了一个星期以 后,我终于初步实现了这个代理服务器。

以前谈起Go,我总是觉得这门语言怪怪的:变量声明反着写,反复的错误处理,import了未使用的包就不能 成功编译……但是这一次发现用Go写Web出奇的好用,内部支持的库就相当强大。以后做Web可以直接用Go了, 连框架都不用。

这个项目其实难度很小,但是最初对Go还是不很熟练,也对代理服务器的原理不是很理解,所以刚开始才一直 在看别人的代码。之后我忽然就顿悟了,代理服务器其实就是一个web服务器,只是他接受请求后将请求转发给 真正要请求的服务器。而浏览器在使用了代理后,发送的包都和正常的http包没有太多区别,只是不直接发送 到请求的URI,而是发送到代理服务器上。所以,代理服务器只是做了一个中间人的工作。

理解了原理之后,我读着Go的官方文档,把自己的想法实现了。不得不说Go的官方很完整,所有的方法都解释 的很清楚。下面来看我写出来的代码:

func main() {
    http.HandleFunc("/", Handler)
    log.Fatal(http.ListenAndServe(":8080", nil))    
}

这里是创建了一个http服务器,监听8080端口,并调用Handler来处理请求。然后我们就要写这个Handler 函数了。

func Handler(w http.ResponseWriter, req *http.Request) {
    fmt.Printf("URL: %s\n", req.URL)
    client := &http.Client{}
    resq, err := http.NewRequest(req.Method, req.URL.String(), req.Body)
    if (err != nil) {
        log.Fatal(err)
    }

    defer resq.Body.Close()
    rsp, err := client.Do(resq)
    if (err != nil) {
        log.Fatal(err)
    }
    fmt.Printf("Status: %s\n", rsp.Status)
    w.WriteHeader(rsp.StatusCode)
    w.Write(rsp.Body)
}

这个函数中创建了一个http客户端,并用接受到请求req的拷贝来请求目的服务器。当接收到回应时,将回应 的内容写给收到的请求。

在最开始写完这个函数的时候,我并不能正常使用这个代理服务器。后来,我才发现Go在实例化Client的时候 调用系统的$HTTP_PROXY环境变量,而Ubuntu的这个环境变量不知为何被修改了,所以才会一直出错。