1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-12-06 10:16:40 -05:00
v2fly/proxy/http/http.go
adoot 09bf6def69 http bug fixes & disable keep-alive
This patch defers Conn.Close call until all responses from server
has been written to the client. It should fix many of the hanging
issues we have with plain HTTP requests.
2016-01-01 13:08:53 -08:00

223 lines
5.5 KiB
Go

package http
import (
"bufio"
"io"
"net"
"net/http"
"strconv"
"strings"
"sync"
"github.com/v2ray/v2ray-core/app"
"github.com/v2ray/v2ray-core/common/alloc"
"github.com/v2ray/v2ray-core/common/log"
v2net "github.com/v2ray/v2ray-core/common/net"
"github.com/v2ray/v2ray-core/transport/ray"
)
type HttpProxyServer struct {
accepting bool
space app.Space
config Config
}
func NewHttpProxyServer(space app.Space, config Config) *HttpProxyServer {
return &HttpProxyServer{
space: space,
config: config,
}
}
func (this *HttpProxyServer) Listen(port v2net.Port) error {
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{
Port: int(port.Value()),
IP: []byte{0, 0, 0, 0},
})
if err != nil {
return err
}
go this.accept(tcpListener)
return nil
}
func (this *HttpProxyServer) accept(listener *net.TCPListener) {
this.accepting = true
for this.accepting {
tcpConn, err := listener.AcceptTCP()
if err != nil {
log.Error("Failed to accept HTTP connection: %v", err)
continue
}
go this.handleConnection(tcpConn)
}
}
func parseHost(rawHost string, defaultPort v2net.Port) (v2net.Destination, error) {
port := defaultPort
host, rawPort, err := net.SplitHostPort(rawHost)
if err != nil {
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
host = rawHost
} else {
return nil, err
}
} else {
intPort, err := strconv.Atoi(rawPort)
if err != nil {
return nil, err
}
port = v2net.Port(intPort)
}
if ip := net.ParseIP(host); ip != nil {
return v2net.TCPDestination(v2net.IPAddress(ip), port), nil
}
return v2net.TCPDestination(v2net.DomainAddress(host), port), nil
}
func (this *HttpProxyServer) handleConnection(conn *net.TCPConn) {
defer conn.Close()
reader := bufio.NewReader(conn)
request, err := http.ReadRequest(reader)
if err != nil {
return
}
log.Info("Request to Method [%s] Host [%s] with URL [%s]", request.Method, request.Host, request.URL.String())
defaultPort := v2net.Port(80)
if strings.ToLower(request.URL.Scheme) == "https" {
defaultPort = v2net.Port(443)
}
host := request.Host
if len(host) == 0 {
host = request.URL.Host
}
dest, err := parseHost(host, defaultPort)
if err != nil {
log.Warning("Malformed proxy host (%s): %v", host, err)
}
if strings.ToUpper(request.Method) == "CONNECT" {
this.handleConnect(request, dest, reader, conn)
} else {
this.handlePlainHTTP(request, dest, reader, conn)
}
}
func (this *HttpProxyServer) handleConnect(request *http.Request, destination v2net.Destination, reader io.Reader, writer io.Writer) {
response := &http.Response{
Status: "200 OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: http.Header(make(map[string][]string)),
Body: nil,
ContentLength: 0,
Close: false,
}
buffer := alloc.NewSmallBuffer().Clear()
response.Write(buffer)
writer.Write(buffer.Value)
buffer.Release()
packet := v2net.NewPacket(destination, nil, true)
ray := this.space.PacketDispatcher().DispatchToOutbound(packet)
this.transport(reader, writer, ray)
}
func (this *HttpProxyServer) transport(input io.Reader, output io.Writer, ray ray.InboundRay) {
var wg sync.WaitGroup
wg.Add(2)
defer wg.Wait()
go func() {
v2net.ReaderToChan(ray.InboundInput(), input)
close(ray.InboundInput())
wg.Done()
}()
go func() {
v2net.ChanToWriter(output, ray.InboundOutput())
wg.Done()
}()
}
func stripHopByHopHeaders(request *http.Request) {
// Strip hop-by-hop header basaed on RFC:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
request.Header.Del("Proxy-Connection")
request.Header.Del("Proxy-Authenticate")
request.Header.Del("Proxy-Authorization")
request.Header.Del("TE")
request.Header.Del("Trailers")
request.Header.Del("Transfer-Encoding")
request.Header.Del("Upgrade")
// TODO: support keep-alive
connections := request.Header.Get("Connection")
request.Header.Set("Connection", "close")
if len(connections) == 0 {
return
}
for _, h := range strings.Split(connections, ",") {
request.Header.Del(strings.TrimSpace(h))
}
}
func (this *HttpProxyServer) handlePlainHTTP(request *http.Request, dest v2net.Destination, reader *bufio.Reader, writer io.Writer) {
if len(request.URL.Host) <= 0 {
hdr := http.Header(make(map[string][]string))
hdr.Set("Connection", "close")
response := &http.Response{
Status: "400 Bad Request",
StatusCode: 400,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: hdr,
Body: nil,
ContentLength: 0,
Close: false,
}
buffer := alloc.NewSmallBuffer().Clear()
response.Write(buffer)
writer.Write(buffer.Value)
buffer.Release()
return
}
request.Host = request.URL.Host
stripHopByHopHeaders(request)
requestBuffer := alloc.NewBuffer().Clear()
request.Write(requestBuffer)
log.Info("Request to remote:\n%s", string(requestBuffer.Value))
packet := v2net.NewPacket(dest, requestBuffer, true)
ray := this.space.PacketDispatcher().DispatchToOutbound(packet)
defer close(ray.InboundInput())
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
responseReader := bufio.NewReader(NewChanReader(ray.InboundOutput()))
responseBuffer := alloc.NewBuffer()
defer responseBuffer.Release()
response, err := http.ReadResponse(responseReader, request)
if err != nil {
return
}
responseBuffer.Clear()
response.Write(responseBuffer)
writer.Write(responseBuffer.Value)
response.Body.Close()
}()
wg.Wait()
}