1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-06-11 02:10:44 +00:00

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.
This commit is contained in:
adoot 2015-12-31 23:34:08 +00:00
parent e19d8002a8
commit 09bf6def69
2 changed files with 144 additions and 64 deletions

View File

@ -79,66 +79,28 @@ func parseHost(rawHost string, defaultPort v2net.Port) (v2net.Destination, error
func (this *HttpProxyServer) handleConnection(conn *net.TCPConn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for true {
request, err := http.ReadRequest(reader)
if err != nil {
break
}
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 if len(request.URL.Host) > 0 {
request.Host = request.URL.Host
request.Header.Set("Connection", "keep-alive")
request.Header.Del("Proxy-Connection")
buffer := alloc.NewBuffer().Clear()
request.Write(buffer)
log.Info("Request to remote: %s", string(buffer.Value))
packet := v2net.NewPacket(dest, buffer, true)
ray := this.space.PacketDispatcher().DispatchToOutbound(packet)
go func() {
defer close(ray.InboundInput())
responseReader := bufio.NewReader(NewChanReader(ray.InboundOutput()))
response, err := http.ReadResponse(responseReader, request)
if err != nil {
return
}
responseBuffer := alloc.NewBuffer().Clear()
defer responseBuffer.Release()
response.Write(responseBuffer)
conn.Write(responseBuffer.Value)
}()
} else {
response := &http.Response{
Status: "400 Bad Request",
StatusCode: 400,
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)
conn.Write(buffer.Value)
buffer.Release()
}
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)
}
}
@ -166,18 +128,95 @@ func (this *HttpProxyServer) handleConnect(request *http.Request, destination v2
}
func (this *HttpProxyServer) transport(input io.Reader, output io.Writer, ray ray.InboundRay) {
var outputFinish sync.Mutex
outputFinish.Lock()
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())
outputFinish.Unlock()
wg.Done()
}()
outputFinish.Lock()
}
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()
}

41
proxy/http/http_test.go Normal file
View File

@ -0,0 +1,41 @@
package http
import (
"bufio"
"github.com/v2ray/v2ray-core/testing/assert"
"net/http"
"strings"
"testing"
)
func TestHopByHopHeadersStrip(t *testing.T) {
var rawRequest = `GET /pkg/net/http/ HTTP/1.1
Host: golang.org
Connection: keep-alive,Foo, Bar
Foo: foo
Bar: bar
Proxy-Connection: keep-alive
Proxy-Authenticate: abc
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; de-de) AppleWebKit/523.10.3 (KHTML, like Gecko) Version/3.0.4 Safari/523.10
Accept-Encoding: gzip
Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7
Cache-Control: no-cache
Accept-Language: de,en;q=0.7,en-us;q=0.3
`
b := bufio.NewReader(strings.NewReader(rawRequest))
req, err := http.ReadRequest(b)
assert.Error(err).IsNil()
assert.StringLiteral(req.Header.Get("Foo")).Equals("foo")
assert.StringLiteral(req.Header.Get("Bar")).Equals("bar")
assert.StringLiteral(req.Header.Get("Connection")).Equals("keep-alive,Foo, Bar")
assert.StringLiteral(req.Header.Get("Proxy-Connection")).Equals("keep-alive")
assert.StringLiteral(req.Header.Get("Proxy-Authenticate")).Equals("abc")
stripHopByHopHeaders(req)
assert.StringLiteral(req.Header.Get("Connection")).Equals("close")
assert.StringLiteral(req.Header.Get("Foo")).Equals("")
assert.StringLiteral(req.Header.Get("Bar")).Equals("")
assert.StringLiteral(req.Header.Get("Proxy-Connection")).Equals("")
assert.StringLiteral(req.Header.Get("Proxy-Authenticate")).Equals("")
}