add http proxy

This commit is contained in:
ambitioner 2017-05-19 16:34:03 +08:00
parent ba6afd5789
commit 9103a78774
9 changed files with 329 additions and 0 deletions

View File

@ -0,0 +1,8 @@
#!/bin/bash
export GOPATH=$GOPATH:$PWD
cd $PWD/src/main
go build -o http-proxy
echo "successfully build,binary executable file in src/main"

View File

@ -0,0 +1,5 @@
{
"port":":8080",
"auth":true,
"user":{"proxy":"proxy"}
}

Binary file not shown.

View File

@ -0,0 +1,14 @@
package main
import (
"proxy"
"github.com/fatedier/frp/utils/log"
)
func main() {
ps := proxy.NewProxyServer()
log.Info("begin proxy")
log.Error("proxy exit %v", ps.ListenAndServe())
}

View File

@ -0,0 +1,98 @@
package proxy
import (
"encoding/base64"
"errors"
"net/http"
"strings"
"github.com/fatedier/frp/utils/log"
)
/*
http://www.checkupdown.com/status/E407_zh.html
HTTP 407 错误 - 要求代理身份验证 (Proxy authentication required)
您的 Web 服务器认为客户端如您的浏览器或我们的 CheckUpDown 机器人发送的 HTTP 数据流是正确的但访问该网址资源需要事先经过一个代理服务器而该代理服务器所需的身份验证尚未提供 这通常意味着您必须首先登录输入用户名和密码代理服务器
通过浏览器检测到的 407 错误往往可以通过选择略有不同的导航途径访问该网址来解决 如先访问该代理服务器的其他网址 您的互联网服务供应商 (ISP) 应该能够解释该代理服务器在其安全设置中的作用以及如何使用
*/
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407
/*
HTTP/1.1 407 Proxy Authentication Required
Date: Wed, 21 Oct 2015 07:28:00 GMT
Proxy-Authenticate: Basic realm="Access to internal site"
*/
var HTTP_407 = []byte("HTTP/1.1 407 Proxy Authorization Required\r\nProxy-Authenticate: Basic realm=\"Access to internal site\"\r\n\r\n")
// 鉴权方法
func (proxy *ProxyServer) Auth(rw http.ResponseWriter, req *http.Request) bool {
var err error
if cnfg.Auth == true { // 代理服务器登入认证
if proxy.Name, err = proxy.auth(rw, req); err != nil {
log.Debug("%s can not successfully access %v", proxy.Name, err)
return true
}
} else {
proxy.Name = "default-proxy"
}
// log.Info("%s successfully log in!", proxy.Name)
return false
}
func (proxy *ProxyServer) auth(rw http.ResponseWriter, req *http.Request) (string, error) {
// get header
// Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
auth := req.Header.Get("Proxy-Authorization")
auth = strings.Replace(auth, "Basic ", "", 1)
if auth == "" {
writeResp(rw, HTTP_407)
return "", errors.New("Need Proxy Authorization!")
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Proxy-Authorization
data, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
log.Debug("when decoding %v, got an error of %v", auth, err)
return "", errors.New("Fail to decoding Proxy-Authorization")
}
var user, passwd string
// username:password
UserPasswdPair := strings.Split(string(data), ":")
if len(UserPasswdPair) != 2 {
writeResp(rw, HTTP_407)
return "", errors.New("Fail to log in")
} else {
user = UserPasswdPair[0]
passwd = UserPasswdPair[1]
}
if check(user, passwd) == false {
writeResp(rw, HTTP_407)
return "", errors.New("Fail to log in")
}
return user, nil
}
func writeResp(rw http.ResponseWriter, data []byte) error {
_, err := rw.Write(data)
if err != nil {
log.Error("fail to write data to response")
return errors.New("InternalServerError")
}
return nil
}
// 验证浏览器输入的proxy是否合法
func check(User, passwd string) bool {
if User != "" && passwd != "" && cnfg.User[User] == passwd {
return true
}
return false
}

View File

@ -0,0 +1,32 @@
package proxy
import (
"bufio"
"encoding/json"
"os"
)
// Config 保存代理服务器的配置
type Config struct {
Port string `json:"port"`
Auth bool `json:"auth"`
User map[string]string `json:"user"`
}
// 从指定json文件读取config配置
func (c *Config) GetConfig(filename string) error {
configFile, err := os.Open(filename)
if err != nil {
return err
}
defer configFile.Close()
br := bufio.NewReader(configFile)
err = json.NewDecoder(br).Decode(c)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,24 @@
package proxy
import (
"os"
"github.com/fatedier/frp/utils/log"
)
var cnfg Config
func init() {
// 加载配置文件
err := cnfg.GetConfig("../config/config.json")
if err != nil {
log.Error("can not load config file:%v\n", err)
os.Exit(-1)
}
setLog()
}
func setLog() {
log.SetLogLevel("debug")
}

View File

@ -0,0 +1,118 @@
package proxy
import (
"fmt"
"io"
"net"
"net/http"
"time"
"util"
"github.com/fatedier/frp/utils/log"
)
var HTTP_200 = []byte("HTTP/1.1 200 Connection Established\r\n\r\n")
type ProxyServer struct {
Tr *http.Transport
Name string
}
func NewProxyServer() *http.Server {
return &http.Server{
Addr: cnfg.Port,
Handler: &ProxyServer{Tr: http.DefaultTransport.(*http.Transport), Name: "default-proxy"},
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
}
// 自动执行的方法因为ProxyServer实现了Handler接口,需要ServerHTTP
func (proxy *ProxyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil {
rw.WriteHeader(http.StatusInternalServerError)
log.Error("Panic: %v", err)
fmt.Fprintf(rw, fmt.Sprintln(err))
}
}()
// 鉴权
if proxy.Auth(rw, req) {
return
}
if req.Method == "CONNECT" { // 是connect连接
proxy.HttpsHandler(rw, req)
} else {
proxy.HttpHandler(rw, req)
}
}
// 处理普通的http请求
func (proxy *ProxyServer) HttpHandler(rw http.ResponseWriter, req *http.Request) {
log.Info("%v is sending request %v %v ", proxy.Name, req.Method, req.URL.Host)
util.RemoveProxyHeaders(req) // 去除不必要的头
resp, err := proxy.Tr.RoundTrip(req)
if err != nil {
log.Error("%s transport RoundTrip error: %v", proxy.Name, err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
util.ClearHeaders(rw.Header()) // 得到一个空的Header
util.CopyHeaders(rw.Header(), resp.Header)
rw.WriteHeader(resp.StatusCode)
nr, err := io.Copy(rw, resp.Body)
if err != nil && err != io.EOF {
log.Error("%v got an error when copy remote response to client.%v", proxy.Name, err)
return
}
log.Info("%v copied %v bytes from remote host %v.", proxy.Name, nr, req.URL.Host)
}
// 处理https连接主要用于CONNECT方法
func (proxy *ProxyServer) HttpsHandler(rw http.ResponseWriter, req *http.Request) {
log.Info("[CONNECT] %v tried to connect to remote host %v", proxy.Name, req.URL.Host)
hj, _ := rw.(http.Hijacker)
client, _, err := hj.Hijack() //获取客户端与代理服务器的tcp连接
if err != nil {
log.Error("%v failed to get Tcp connection of", proxy.Name, req.RequestURI)
http.Error(rw, "Failed", http.StatusBadRequest)
return
}
remote, err := net.Dial("tcp", req.URL.Host) //建立服务端和代理服务器的tcp连接
if err != nil {
log.Error("%v failed to connect %v", proxy.Name, req.RequestURI)
http.Error(rw, "Failed", http.StatusBadRequest)
client.Close()
return
}
client.Write(HTTP_200)
go copyRemoteToClient(proxy.Name, remote, client)
go copyRemoteToClient(proxy.Name, client, remote)
}
// data copy between two socket
func copyRemoteToClient(Name string, remote, client net.Conn) {
defer func() {
remote.Close()
client.Close()
}()
nr, err := io.Copy(remote, client)
if err != nil && err != io.EOF {
log.Error("%v got an error when handles CONNECT %v", Name, err)
return
}
log.Info("[CONNECT] %v transported %v bytes betwwen %v and %v", Name, nr, remote.RemoteAddr(), client.RemoteAddr())
}

View File

@ -0,0 +1,30 @@
package util
import "net/http"
func CopyHeaders(dst, src http.Header) {
for key, values := range src {
for _, value := range values {
dst.Add(key, value)
}
}
}
func ClearHeaders(headers http.Header) {
for key, _ := range headers {
headers.Del(key)
}
}
func RemoveProxyHeaders(req *http.Request) {
req.RequestURI = ""
req.Header.Del("Proxy-Connection")
req.Header.Del("Connection")
req.Header.Del("Keep-Alive")
req.Header.Del("Proxy-Authenticate")
req.Header.Del("Proxy-Authorization")
req.Header.Del("TE")
req.Header.Del("Trailers")
req.Header.Del("Transfer-Encoding")
req.Header.Del("Upgrade")
}