You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

174 lines
4.5 KiB

# -*- coding: utf-8 -*-
from base import *
from requests import *
import socketserver
import threading
class HttpBackendHandler(socketserver.StreamRequestHandler):
def handle(self):
keepalive = True
while True:
reqline = self.rfile.readline().decode('utf-8').rstrip()
# eprint("Request line: " + repr(reqline))
reqline = reqline.split(' ', 3)
if len(reqline) != 3 or reqline[0].upper() != 'GET':
self.wfile.write(b"HTTP/1.0 400 Bad request\r\n\r\n")
return
keepalive_default = True
if reqline[2].upper() != "HTTP/1.1":
keepalive = False
keepalive_default = False
if reqline[1].startswith("/keepalive"):
# simulate broken backend (HTTP/1.0 incompatible)
keepalive = True
keepalive_default = True
# read headers; and GET has no body
while True:
hdr = self.rfile.readline().decode('utf-8').rstrip()
if hdr == "": break
hdr = hdr.split(':', 2)
if hdr[0].lower() == "connection":
keepalive = (hdr[1].strip().lower() == "keep-alive")
if reqline[1].startswith("/upgrade/custom"):
self.wfile.write(b"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: custom\r\n\r\nHello World!")
return
if reqline[1].startswith("/chunked/delay"):
import time
self.wfile.write(b"HTTP/1.1 200 Ok\r\nTransfer-Encoding: chunked\r\n\r\n4\r\nHi")
time.sleep(0.1)
self.wfile.write(b"!\n")
time.sleep(0.1)
self.wfile.write(b"\r\n0\r\n\r\n")
continue
if reqline[1].startswith("/nolength"):
self.wfile.write(b"HTTP/1.1 200 OK\r\n\r\nHello world")
self.finish()
return
# send response
resp_body = reqline[1].encode('utf-8')
clen = "Content-Length: {}\r\n".format(len(resp_body)).encode('utf-8')
ka = b""
if keepalive != keepalive_default:
if keepalive:
ka = b"Connection: keep-alive\r\n"
else:
ka = b"Connection: close\r\n"
resp = b"HTTP/1.1 200 OK\r\n" + ka + clen + b"\r\n" + resp_body
# eprint("Backend response: " + repr(resp_body))
self.wfile.write(resp)
if not keepalive:
return
class HttpBackend(socketserver.ThreadingMixIn, socketserver.TCPServer):
allow_reuse_address = True
def __init__(self):
self.port = Env.port + 3
super().__init__(('127.0.0.2', self.port), HttpBackendHandler)
self.listen_thread = threading.Thread(target = self.serve_forever, name = "HttpBackend-{}".format(self.port))
self.listen_thread.daemon = True
self.listen_thread.start()
class TestSimple(CurlRequest):
URL = "/test.txt"
EXPECT_RESPONSE_CODE = 200
EXPECT_RESPONSE_BODY = TEST_TXT
EXPECT_RESPONSE_HEADERS = [("Content-Type", "text/plain; charset=utf-8")]
config = """
req_header.overwrite "Host" => "basic-gets";
self_proxy;
"""
no_docroot = True
# backend gets encoded %2F
class TestProxiedRewrittenEncodedURL(CurlRequest):
URL = "/foo%2Ffile?abc"
EXPECT_RESPONSE_BODY = "/dest%2Ffile?abc"
EXPECT_RESPONSE_CODE = 200
no_docroot = True
config = """
rewrite_raw "/foo(.*)" => "/dest$1";
backend_proxy;
"""
# backend gets decoded %2F
class TestProxiedRewrittenDecodedURL(CurlRequest):
URL = "/foo%2Ffile?abc"
EXPECT_RESPONSE_BODY = "/dest/file?abc"
EXPECT_RESPONSE_CODE = 200
no_docroot = True
config = """
rewrite "/foo(.*)" => "/dest$1";
backend_proxy;
"""
# fake a backend forcing keep-alive mode
class TestBackendForcedKeepalive(CurlRequest):
URL = "/keepalive"
EXPECT_RESPONSE_BODY = "/keepalive"
EXPECT_RESPONSE_CODE = 200
no_docroot = True
config = """
backend_proxy;
"""
# have backend "Upgrade"
class TestBackendUpgrade(CurlRequest):
URL = "/upgrade/custom"
EXPECT_RESPONSE_BODY = "Hello World!"
EXPECT_RESPONSE_CODE = 101
no_docroot = True
config = """
backend_proxy;
"""
class TestBackendDelayedChunk(CurlRequest):
URL = "/chunked/delay"
EXPECT_RESPONSE_BODY = "Hi!\n"
EXPECT_RESPONSE_CODE = 200
no_docroot = True
config = """
backend_proxy;
"""
class TestBackendNoLength(CurlRequest):
URL = "/nolength"
EXPECT_RESPONSE_BODY = "Hello world"
EXPECT_RESPONSE_CODE = 200
no_docroot = True
config = """
backend_proxy;
"""
class Test(GroupTest):
group = [
TestSimple,
TestProxiedRewrittenEncodedURL,
TestProxiedRewrittenDecodedURL,
TestBackendForcedKeepalive,
TestBackendUpgrade,
TestBackendDelayedChunk,
TestBackendNoLength,
]
def Prepare(self):
self.http_backend = HttpBackend()
self.plain_config = """
setup {{ module_load "mod_proxy"; }}
self_proxy = {{
proxy "127.0.0.2:{self_port}";
}};
backend_proxy = {{
proxy "127.0.0.2:{backend_port}";
}};
""".format(
self_port = Env.port,
backend_port = self.http_backend.port,
)
def Cleanup(self):
self.http_backend.shutdown()