1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-12-06 10:16:40 -05:00
v2fly/proxy/http/client.go
2019-07-24 09:15:05 +08:00

148 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package http
import (
"context"
"encoding/base64"
"io"
"strings"
"v2ray.com/core"
"v2ray.com/core/common"
"v2ray.com/core/common/buf"
"v2ray.com/core/common/net"
"v2ray.com/core/common/protocol"
"v2ray.com/core/common/retry"
"v2ray.com/core/common/session"
"v2ray.com/core/common/signal"
"v2ray.com/core/common/task"
"v2ray.com/core/features/policy"
"v2ray.com/core/transport"
"v2ray.com/core/transport/internet"
)
type Client struct {
serverPicker protocol.ServerPicker
policyManager policy.Manager
}
// NewClient create a new http client based on the given config.
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
serverList := protocol.NewServerList()
for _, rec := range config.Server {
s, err := protocol.NewServerSpecFromPB(*rec)
if err != nil {
return nil, newError("failed to get server spec").Base(err)
}
serverList.AddServer(s)
}
if serverList.Size() == 0 {
return nil, newError("0 target server")
}
v := core.MustFromContext(ctx)
return &Client{
serverPicker: protocol.NewRoundRobinServerPicker(serverList),
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}, nil
}
// Process implements proxy.Outbound.Process.
// 使用connect方法连接http代理服务器获得一个隧道然后通过该隧道通信
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
outbound := session.OutboundFromContext(ctx)
if outbound == nil || !outbound.Target.IsValid() {
return newError("target not specified.")
}
destination := outbound.Target
if destination.Network == net.Network_UDP {
return newError("UDP is not supported by HTTP outbound")
}
var server *protocol.ServerSpec
var conn internet.Connection
if err := retry.ExponentialBackoff(5, 100).On(func() error {
server = c.serverPicker.PickServer()
dest := server.Destination()
rawConn, err := dialer.Dial(ctx, dest)
if err != nil {
return err
}
conn = rawConn
return nil
}); err != nil {
return newError("failed to find an available destination").Base(err)
}
defer func() {
if err := conn.Close(); err != nil {
newError("failed to closed connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
}
}()
p := c.policyManager.ForLevel(0)
user := server.PickUser()
if user != nil {
p = c.policyManager.ForLevel(user.Level)
}
if err := setUpHttpTunnel(conn, conn, &destination, user); err != nil {
return err
}
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, p.Timeouts.ConnectionIdle)
requestFunc := func() error {
defer timer.SetTimeout(p.Timeouts.DownlinkOnly)
return buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))
}
responseFunc := func() error {
defer timer.SetTimeout(p.Timeouts.UplinkOnly)
return buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))
}
var responseDonePost = task.OnSuccess(responseFunc, task.Close(link.Writer))
if err := task.Run(ctx, requestFunc, responseDonePost); err != nil {
return newError("connection ends").Base(err)
}
return nil
}
// 使用http connect方法建立一个隧道
func setUpHttpTunnel(reader io.Reader, writer io.Writer, destination *net.Destination, user *protocol.MemoryUser) error {
var headers []string
destNetAddr := destination.NetAddr()
headers = append(headers, "CONNECT "+destNetAddr+" HTTP/1.1")
headers = append(headers, "Host: "+destNetAddr)
if user != nil && user.Account != nil {
account := user.Account.(*Account)
auth := account.GetUsername() + ":" + account.GetPassword()
headers = append(headers, "Proxy-Authorization: Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
}
headers = append(headers, "Proxy-Connection: Keep-Alive")
b := buf.New()
b.WriteString(strings.Join(headers, "\r\n") + "\r\n\r\n")
if err := buf.WriteAllBytes(writer, b.Bytes()); err != nil {
return err
}
b.Clear()
if _, err := b.ReadFrom(reader); err != nil {
return err
}
return nil
}
func init() {
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewClient(ctx, config.(*ClientConfig))
}))
}