Files
proxy/gateway/fwd/http/http.go

186 lines
3.9 KiB
Go

package http
import (
"bufio"
"context"
"encoding/base64"
"fmt"
"github.com/google/uuid"
"io"
"net"
"net/textproto"
"net/url"
"proxy-server/gateway/core"
"proxy-server/gateway/fwd/auth"
"strings"
"errors"
)
type Request struct {
conn net.Conn
reader *bufio.Reader
method string
uri string
proto string
headers *textproto.MIMEHeader
auth *core.AuthContext
dest *core.FwdAddr
}
func Process(ctx context.Context, conn net.Conn) (*core.Conn, error) {
reader := bufio.NewReader(conn)
textReader := textproto.NewReader(reader)
// 首行
line, err := textReader.ReadLine()
if err != nil {
return nil, err
}
parts := strings.Fields(line)
if len(parts) != 3 {
return nil, errors.New("无效的 http 请求")
}
// 请求头
headers, err := textReader.ReadMIMEHeader()
if err != nil {
return nil, fmt.Errorf("解析请求头失败: %v", err)
}
// 验证账号
authInfo := headers.Get("Proxy-Authorization")
var username *string = nil
var password *string = nil
if authInfo != "" {
authParts := strings.Split(authInfo, " ")
if len(authParts) != 2 {
return nil, errors.New("无效的 Proxy-Authorization")
}
if authParts[0] != "Basic" {
return nil, errors.New("不支持的认证方式")
}
authBytes, err := base64.URLEncoding.DecodeString(authParts[1])
if err != nil {
return nil, fmt.Errorf("解码认证信息失败: %v", err)
}
authPair := strings.Split(string(authBytes), ":")
username = &authPair[0]
password = &authPair[1]
}
authCtx, err := auth.Protect(conn, auth.Http, username, password)
if err != nil {
_, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n\r\n"))
if err != nil {
return nil, fmt.Errorf("响应 407 失败: %v", err)
}
return nil, fmt.Errorf("验证账号失败: %v", err)
}
// 获取 Host
host := headers.Get("Host")
if host == "" {
return nil, errors.New("无效的 Host")
}
if !strings.Contains(host, ":") {
host += ":80"
}
addr, err := net.ResolveTCPAddr("tcp", host)
if err != nil {
return nil, fmt.Errorf("解析 Host 失败: %v", err)
}
request := &Request{
conn: conn,
reader: reader,
method: parts[0],
uri: parts[1],
proto: parts[2],
headers: &headers,
dest: &core.FwdAddr{
IP: addr.IP,
Port: addr.Port,
Domain: host,
},
auth: authCtx,
}
var user *core.Conn
if parts[0] == "CONNECT" {
user, err = processHttps(ctx, request)
if err != nil {
return nil, err
}
} else {
user, err = processHttp(ctx, request)
if err != nil {
return nil, err
}
}
return user, nil
}
func processHttps(ctx context.Context, req *Request) (*core.Conn, error) {
// 响应 CONNECT
_, err := req.conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
if err != nil {
return nil, fmt.Errorf("响应 CONNECT 失败: %v", err)
}
return &core.Conn{
Conn: req.conn,
Reader: req.reader,
Tag: uuid.New(),
Protocol: "http",
Dest: req.dest,
Auth: req.auth,
}, nil
}
func processHttp(ctx context.Context, req *Request) (*core.Conn, error) {
// 修改请求头
rawUrl, err := url.Parse(req.uri)
if err != nil {
return nil, fmt.Errorf("解析请求地址失败: %v", err)
}
rawUrl.Scheme = ""
rawUrl.Host = ""
req.uri = rawUrl.String()
req.headers.Del("Proxy-Authorization")
// 构造请求
sb := strings.Builder{}
sb.WriteString(req.method)
sb.WriteString(" ")
sb.WriteString(req.uri)
sb.WriteString(" ")
sb.WriteString(req.proto)
sb.WriteString("\r\n")
for k, v := range *req.headers {
sb.WriteString(k)
sb.WriteString(": ")
sb.WriteString(strings.Join(v, ","))
sb.WriteString("\r\n")
}
sb.WriteString("\r\n")
mReader := io.MultiReader(strings.NewReader(sb.String()), req.conn)
newReader := bufio.NewReader(mReader)
return &core.Conn{
Conn: req.conn,
Reader: newReader,
Tag: uuid.New(),
Protocol: "http",
Dest: req.dest,
Auth: req.auth,
}, nil
}