Verified Commit adce81e5 authored by Dmitry Volodin's avatar Dmitry Volodin
Browse files

#403 web: Prevent ioloop KeyError race condition on high concurrence levels

parent a380ebdb
Pipeline #2838 passed with stage
in 17 seconds
......@@ -13,6 +13,8 @@ import tornado.httpserver
import tornado.gen
import tornado.wsgi
import django.core.handlers.wsgi
from tornado import escape
from tornado import httputil
# NOC modules
from noc.config import config
from noc.core.service.base import Service
......@@ -59,10 +61,6 @@ class WebService(Service):
class NOCWSGIHandler(tornado.web.RequestHandler):
def initialize(self, service):
self.service = service
self.wsgi = NOCWSGIContainer(
self.service,
django.core.handlers.wsgi.WSGIHandler()
)
self.executor = self.service.get_executor("max")
self.backend_id = "%s (%s:%s)" % (
self.service.service_id,
......@@ -70,17 +68,61 @@ class NOCWSGIHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def prepare(self):
self.set_header("X-NOC-Backend", self.backend_id)
yield self.executor.submit(self.wsgi, self.request)
data = yield self.process_request(self.request)
header_obj = httputil.HTTPHeaders()
for key, value in data["headers"]:
header_obj.add(key, value)
self.request.connection.write_headers(
data["start_line"], header_obj, chunk=data["body"]
)
self.request.connection.finish()
self.log_request(data["status_code"], self.request)
self._finished = True
class NOCWSGIContainer(tornado.wsgi.WSGIContainer):
def __init__(self, service, wsgi):
super(NOCWSGIContainer, self).__init__(wsgi)
self.service = service
def _log(self, status_code, request):
@tornado.gen.coroutine
def process_request(self, request):
data = {}
response = []
def start_response(status, response_headers, exc_info=None):
data["status"] = status
data["headers"] = response_headers
return response.append
wsgi = django.core.handlers.wsgi.WSGIHandler()
app_response = yield self.executor.submit(
wsgi,
tornado.wsgi.WSGIContainer.environ(request),
start_response
)
try:
response.extend(app_response)
body = b"".join(response)
finally:
if hasattr(app_response, "close"):
app_response.close()
if not data:
raise Exception("WSGI app did not call start_response")
status_code, reason = data["status"].split(' ', 1)
status_code = int(status_code)
headers = data["headers"]
header_set = set(k.lower() for (k, v) in headers)
body = escape.utf8(body)
if status_code != 304:
if "content-length" not in header_set:
headers.append(("Content-Length", str(len(body))))
if "content-type" not in header_set:
headers.append(("Content-Type", "text/html; charset=UTF-8"))
if "server" not in header_set:
headers.append(("Server", "TornadoServer/%s" % tornado.version))
headers.append(("X-NOC-Backend", self.backend_id))
data["status_code"] = status_code
data["start_line"] = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
data["body"] = body
raise tornado.gen.Return(data)
def log_request(self, status_code, request):
method = request.method
uri = request.uri
remote_ip = request.remote_ip
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment