add http proxy
This commit is contained in:
parent
ba6afd5789
commit
9103a78774
8
models/plugin/http-proxy/install.sh
Executable file
8
models/plugin/http-proxy/install.sh
Executable 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"
|
5
models/plugin/http-proxy/src/config/config.json
Normal file
5
models/plugin/http-proxy/src/config/config.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"port":":8080",
|
||||||
|
"auth":true,
|
||||||
|
"user":{"proxy":"proxy"}
|
||||||
|
}
|
BIN
models/plugin/http-proxy/src/main/http-proxy
Executable file
BIN
models/plugin/http-proxy/src/main/http-proxy
Executable file
Binary file not shown.
14
models/plugin/http-proxy/src/main/main.go
Normal file
14
models/plugin/http-proxy/src/main/main.go
Normal 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())
|
||||||
|
}
|
98
models/plugin/http-proxy/src/proxy/auth.go
Normal file
98
models/plugin/http-proxy/src/proxy/auth.go
Normal 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
|
||||||
|
}
|
32
models/plugin/http-proxy/src/proxy/config.go
Normal file
32
models/plugin/http-proxy/src/proxy/config.go
Normal 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
|
||||||
|
}
|
24
models/plugin/http-proxy/src/proxy/init.go
Normal file
24
models/plugin/http-proxy/src/proxy/init.go
Normal 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")
|
||||||
|
}
|
118
models/plugin/http-proxy/src/proxy/proxy.go
Normal file
118
models/plugin/http-proxy/src/proxy/proxy.go
Normal 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())
|
||||||
|
}
|
30
models/plugin/http-proxy/src/util/header_util.go
Normal file
30
models/plugin/http-proxy/src/util/header_util.go
Normal 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")
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user