wsgi.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927
  1. import errno
  2. import functools
  3. import os
  4. import sys
  5. import time
  6. import traceback
  7. import types
  8. import warnings
  9. from eventlet import greenio
  10. from eventlet import greenpool
  11. from eventlet import support
  12. from eventlet.green import BaseHTTPServer
  13. from eventlet.green import socket
  14. from eventlet.support import six
  15. from eventlet.support.six.moves import urllib
  16. DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024
  17. DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1'
  18. MAX_REQUEST_LINE = 8192
  19. MAX_HEADER_LINE = 8192
  20. MAX_TOTAL_HEADER_SIZE = 65536
  21. MINIMUM_CHUNK_SIZE = 4096
  22. # %(client_port)s is also available
  23. DEFAULT_LOG_FORMAT = ('%(client_ip)s - - [%(date_time)s] "%(request_line)s"'
  24. ' %(status_code)s %(body_length)s %(wall_seconds).6f')
  25. is_accepting = True
  26. __all__ = ['server', 'format_date_time']
  27. # Weekday and month names for HTTP date/time formatting; always English!
  28. _weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
  29. _monthname = [None, # Dummy so we can use 1-based month numbers
  30. "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  31. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
  32. def format_date_time(timestamp):
  33. """Formats a unix timestamp into an HTTP standard string."""
  34. year, month, day, hh, mm, ss, wd, _y, _z = time.gmtime(timestamp)
  35. return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
  36. _weekdayname[wd], day, _monthname[month], year, hh, mm, ss
  37. )
  38. def addr_to_host_port(addr):
  39. host = 'unix'
  40. port = ''
  41. if isinstance(addr, tuple):
  42. host = addr[0]
  43. port = addr[1]
  44. return (host, port)
  45. # Collections of error codes to compare against. Not all attributes are set
  46. # on errno module on all platforms, so some are literals :(
  47. BAD_SOCK = set((errno.EBADF, 10053))
  48. BROKEN_SOCK = set((errno.EPIPE, errno.ECONNRESET))
  49. class ChunkReadError(ValueError):
  50. pass
  51. # special flag return value for apps
  52. class _AlreadyHandled(object):
  53. def __iter__(self):
  54. return self
  55. def next(self):
  56. raise StopIteration
  57. __next__ = next
  58. ALREADY_HANDLED = _AlreadyHandled()
  59. class Input(object):
  60. def __init__(self,
  61. rfile,
  62. content_length,
  63. sock,
  64. wfile=None,
  65. wfile_line=None,
  66. chunked_input=False):
  67. self.rfile = rfile
  68. self._sock = sock
  69. if content_length is not None:
  70. content_length = int(content_length)
  71. self.content_length = content_length
  72. self.wfile = wfile
  73. self.wfile_line = wfile_line
  74. self.position = 0
  75. self.chunked_input = chunked_input
  76. self.chunk_length = -1
  77. # (optional) headers to send with a "100 Continue" response. Set by
  78. # calling set_hundred_continue_respose_headers() on env['wsgi.input']
  79. self.hundred_continue_headers = None
  80. self.is_hundred_continue_response_sent = False
  81. def send_hundred_continue_response(self):
  82. towrite = []
  83. # 100 Continue status line
  84. towrite.append(self.wfile_line)
  85. # Optional headers
  86. if self.hundred_continue_headers is not None:
  87. # 100 Continue headers
  88. for header in self.hundred_continue_headers:
  89. towrite.append(six.b('%s: %s\r\n' % header))
  90. # Blank line
  91. towrite.append(b'\r\n')
  92. self.wfile.writelines(towrite)
  93. self.wfile.flush()
  94. # Reinitialize chunk_length (expect more data)
  95. self.chunk_length = -1
  96. def _do_read(self, reader, length=None):
  97. if self.wfile is not None and not self.is_hundred_continue_response_sent:
  98. # 100 Continue response
  99. self.send_hundred_continue_response()
  100. self.is_hundred_continue_response_sent = True
  101. if (self.content_length is not None) and (
  102. length is None or length > self.content_length - self.position):
  103. length = self.content_length - self.position
  104. if not length:
  105. return b''
  106. try:
  107. read = reader(length)
  108. except greenio.SSL.ZeroReturnError:
  109. read = b''
  110. self.position += len(read)
  111. return read
  112. def _chunked_read(self, rfile, length=None, use_readline=False):
  113. if self.wfile is not None and not self.is_hundred_continue_response_sent:
  114. # 100 Continue response
  115. self.send_hundred_continue_response()
  116. self.is_hundred_continue_response_sent = True
  117. try:
  118. if length == 0:
  119. return ""
  120. if length and length < 0:
  121. length = None
  122. if use_readline:
  123. reader = self.rfile.readline
  124. else:
  125. reader = self.rfile.read
  126. response = []
  127. while self.chunk_length != 0:
  128. maxreadlen = self.chunk_length - self.position
  129. if length is not None and length < maxreadlen:
  130. maxreadlen = length
  131. if maxreadlen > 0:
  132. data = reader(maxreadlen)
  133. if not data:
  134. self.chunk_length = 0
  135. raise IOError("unexpected end of file while parsing chunked data")
  136. datalen = len(data)
  137. response.append(data)
  138. self.position += datalen
  139. if self.chunk_length == self.position:
  140. rfile.readline()
  141. if length is not None:
  142. length -= datalen
  143. if length == 0:
  144. break
  145. if use_readline and data[-1] == "\n":
  146. break
  147. else:
  148. try:
  149. self.chunk_length = int(rfile.readline().split(b";", 1)[0], 16)
  150. except ValueError as err:
  151. raise ChunkReadError(err)
  152. self.position = 0
  153. if self.chunk_length == 0:
  154. rfile.readline()
  155. except greenio.SSL.ZeroReturnError:
  156. pass
  157. return b''.join(response)
  158. def read(self, length=None):
  159. if self.chunked_input:
  160. return self._chunked_read(self.rfile, length)
  161. return self._do_read(self.rfile.read, length)
  162. def readline(self, size=None):
  163. if self.chunked_input:
  164. return self._chunked_read(self.rfile, size, True)
  165. else:
  166. return self._do_read(self.rfile.readline, size)
  167. def readlines(self, hint=None):
  168. return self._do_read(self.rfile.readlines, hint)
  169. def __iter__(self):
  170. return iter(self.read, b'')
  171. def get_socket(self):
  172. return self._sock
  173. def set_hundred_continue_response_headers(self, headers,
  174. capitalize_response_headers=True):
  175. # Response headers capitalization (default)
  176. # CONTent-TYpe: TExt/PlaiN -> Content-Type: TExt/PlaiN
  177. # Per HTTP RFC standard, header name is case-insensitive.
  178. # Please, fix your client to ignore header case if possible.
  179. if capitalize_response_headers:
  180. headers = [
  181. ('-'.join([x.capitalize() for x in key.split('-')]), value)
  182. for key, value in headers]
  183. self.hundred_continue_headers = headers
  184. def discard(self, buffer_size=16 << 10):
  185. while self.read(buffer_size):
  186. pass
  187. class HeaderLineTooLong(Exception):
  188. pass
  189. class HeadersTooLarge(Exception):
  190. pass
  191. def get_logger(log, debug):
  192. if callable(getattr(log, 'info', None)) \
  193. and callable(getattr(log, 'debug', None)):
  194. return log
  195. else:
  196. return LoggerFileWrapper(log or sys.stderr, debug)
  197. class LoggerNull(object):
  198. def __init__(self):
  199. pass
  200. def error(self, msg, *args, **kwargs):
  201. pass
  202. def info(self, msg, *args, **kwargs):
  203. pass
  204. def debug(self, msg, *args, **kwargs):
  205. pass
  206. def write(self, msg, *args):
  207. pass
  208. class LoggerFileWrapper(LoggerNull):
  209. def __init__(self, log, debug):
  210. self.log = log
  211. self._debug = debug
  212. def error(self, msg, *args, **kwargs):
  213. self.write(msg, *args)
  214. def info(self, msg, *args, **kwargs):
  215. self.write(msg, *args)
  216. def debug(self, msg, *args, **kwargs):
  217. if self._debug:
  218. self.write(msg, *args)
  219. def write(self, msg, *args):
  220. msg = msg + '\n'
  221. if args:
  222. msg = msg % args
  223. self.log.write(msg)
  224. class FileObjectForHeaders(object):
  225. def __init__(self, fp):
  226. self.fp = fp
  227. self.total_header_size = 0
  228. def readline(self, size=-1):
  229. sz = size
  230. if size < 0:
  231. sz = MAX_HEADER_LINE
  232. rv = self.fp.readline(sz)
  233. if len(rv) >= MAX_HEADER_LINE:
  234. raise HeaderLineTooLong()
  235. self.total_header_size += len(rv)
  236. if self.total_header_size > MAX_TOTAL_HEADER_SIZE:
  237. raise HeadersTooLarge()
  238. return rv
  239. class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler):
  240. protocol_version = 'HTTP/1.1'
  241. minimum_chunk_size = MINIMUM_CHUNK_SIZE
  242. capitalize_response_headers = True
  243. # https://github.com/eventlet/eventlet/issues/295
  244. # Stdlib default is 0 (unbuffered), but then `wfile.writelines()` looses data
  245. # so before going back to unbuffered, remove any usage of `writelines`.
  246. wbufsize = 16 << 10
  247. def setup(self):
  248. # overriding SocketServer.setup to correctly handle SSL.Connection objects
  249. conn = self.connection = self.request
  250. # TCP_QUICKACK is a better alternative to disabling Nagle's algorithm
  251. # https://news.ycombinator.com/item?id=10607422
  252. if getattr(socket, 'TCP_QUICKACK', None):
  253. try:
  254. conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, True)
  255. except socket.error:
  256. pass
  257. try:
  258. self.rfile = conn.makefile('rb', self.rbufsize)
  259. self.wfile = conn.makefile('wb', self.wbufsize)
  260. except (AttributeError, NotImplementedError):
  261. if hasattr(conn, 'send') and hasattr(conn, 'recv'):
  262. # it's an SSL.Connection
  263. self.rfile = socket._fileobject(conn, "rb", self.rbufsize)
  264. self.wfile = socket._fileobject(conn, "wb", self.wbufsize)
  265. else:
  266. # it's a SSLObject, or a martian
  267. raise NotImplementedError("wsgi.py doesn't support sockets "
  268. "of type %s" % type(conn))
  269. def handle_one_request(self):
  270. if self.server.max_http_version:
  271. self.protocol_version = self.server.max_http_version
  272. if self.rfile.closed:
  273. self.close_connection = 1
  274. return
  275. try:
  276. self.raw_requestline = self.rfile.readline(self.server.url_length_limit)
  277. if len(self.raw_requestline) == self.server.url_length_limit:
  278. self.wfile.write(
  279. b"HTTP/1.0 414 Request URI Too Long\r\n"
  280. b"Connection: close\r\nContent-length: 0\r\n\r\n")
  281. self.close_connection = 1
  282. return
  283. except greenio.SSL.ZeroReturnError:
  284. self.raw_requestline = ''
  285. except socket.error as e:
  286. if support.get_errno(e) not in BAD_SOCK:
  287. raise
  288. self.raw_requestline = ''
  289. if not self.raw_requestline:
  290. self.close_connection = 1
  291. return
  292. orig_rfile = self.rfile
  293. try:
  294. self.rfile = FileObjectForHeaders(self.rfile)
  295. if not self.parse_request():
  296. return
  297. except HeaderLineTooLong:
  298. self.wfile.write(
  299. b"HTTP/1.0 400 Header Line Too Long\r\n"
  300. b"Connection: close\r\nContent-length: 0\r\n\r\n")
  301. self.close_connection = 1
  302. return
  303. except HeadersTooLarge:
  304. self.wfile.write(
  305. b"HTTP/1.0 400 Headers Too Large\r\n"
  306. b"Connection: close\r\nContent-length: 0\r\n\r\n")
  307. self.close_connection = 1
  308. return
  309. finally:
  310. self.rfile = orig_rfile
  311. content_length = self.headers.get('content-length')
  312. if content_length is not None:
  313. try:
  314. int(content_length)
  315. except ValueError:
  316. self.wfile.write(
  317. b"HTTP/1.0 400 Bad Request\r\n"
  318. b"Connection: close\r\nContent-length: 0\r\n\r\n")
  319. self.close_connection = 1
  320. return
  321. self.environ = self.get_environ()
  322. self.application = self.server.app
  323. try:
  324. self.server.outstanding_requests += 1
  325. try:
  326. self.handle_one_response()
  327. except socket.error as e:
  328. # Broken pipe, connection reset by peer
  329. if support.get_errno(e) not in BROKEN_SOCK:
  330. raise
  331. finally:
  332. self.server.outstanding_requests -= 1
  333. def handle_one_response(self):
  334. start = time.time()
  335. headers_set = []
  336. headers_sent = []
  337. wfile = self.wfile
  338. result = None
  339. use_chunked = [False]
  340. length = [0]
  341. status_code = [200]
  342. def write(data):
  343. towrite = []
  344. if not headers_set:
  345. raise AssertionError("write() before start_response()")
  346. elif not headers_sent:
  347. status, response_headers = headers_set
  348. headers_sent.append(1)
  349. header_list = [header[0].lower() for header in response_headers]
  350. towrite.append(six.b('%s %s\r\n' % (self.protocol_version, status)))
  351. for header in response_headers:
  352. towrite.append(six.b('%s: %s\r\n' % header))
  353. # send Date header?
  354. if 'date' not in header_list:
  355. towrite.append(six.b('Date: %s\r\n' % (format_date_time(time.time()),)))
  356. client_conn = self.headers.get('Connection', '').lower()
  357. send_keep_alive = False
  358. if self.close_connection == 0 and \
  359. self.server.keepalive and (client_conn == 'keep-alive' or
  360. (self.request_version == 'HTTP/1.1' and
  361. not client_conn == 'close')):
  362. # only send keep-alives back to clients that sent them,
  363. # it's redundant for 1.1 connections
  364. send_keep_alive = (client_conn == 'keep-alive')
  365. self.close_connection = 0
  366. else:
  367. self.close_connection = 1
  368. if 'content-length' not in header_list:
  369. if self.request_version == 'HTTP/1.1':
  370. use_chunked[0] = True
  371. towrite.append(b'Transfer-Encoding: chunked\r\n')
  372. elif 'content-length' not in header_list:
  373. # client is 1.0 and therefore must read to EOF
  374. self.close_connection = 1
  375. if self.close_connection:
  376. towrite.append(b'Connection: close\r\n')
  377. elif send_keep_alive:
  378. towrite.append(b'Connection: keep-alive\r\n')
  379. towrite.append(b'\r\n')
  380. # end of header writing
  381. if use_chunked[0]:
  382. # Write the chunked encoding
  383. towrite.append(six.b("%x" % (len(data),)) + b"\r\n" + data + b"\r\n")
  384. else:
  385. towrite.append(data)
  386. wfile.writelines(towrite)
  387. wfile.flush()
  388. length[0] = length[0] + sum(map(len, towrite))
  389. def start_response(status, response_headers, exc_info=None):
  390. status_code[0] = status.split()[0]
  391. if exc_info:
  392. try:
  393. if headers_sent:
  394. # Re-raise original exception if headers sent
  395. six.reraise(exc_info[0], exc_info[1], exc_info[2])
  396. finally:
  397. # Avoid dangling circular ref
  398. exc_info = None
  399. # Response headers capitalization
  400. # CONTent-TYpe: TExt/PlaiN -> Content-Type: TExt/PlaiN
  401. # Per HTTP RFC standard, header name is case-insensitive.
  402. # Please, fix your client to ignore header case if possible.
  403. if self.capitalize_response_headers:
  404. response_headers = [
  405. ('-'.join([x.capitalize() for x in key.split('-')]), value)
  406. for key, value in response_headers]
  407. headers_set[:] = [status, response_headers]
  408. return write
  409. try:
  410. try:
  411. result = self.application(self.environ, start_response)
  412. if (isinstance(result, _AlreadyHandled)
  413. or isinstance(getattr(result, '_obj', None), _AlreadyHandled)):
  414. self.close_connection = 1
  415. return
  416. # Set content-length if possible
  417. if not headers_sent and hasattr(result, '__len__') and \
  418. 'Content-Length' not in [h for h, _v in headers_set[1]]:
  419. headers_set[1].append(('Content-Length', str(sum(map(len, result)))))
  420. towrite = []
  421. towrite_size = 0
  422. just_written_size = 0
  423. minimum_write_chunk_size = int(self.environ.get(
  424. 'eventlet.minimum_write_chunk_size', self.minimum_chunk_size))
  425. for data in result:
  426. if len(data) == 0:
  427. continue
  428. if isinstance(data, six.text_type):
  429. data = data.encode('ascii')
  430. towrite.append(data)
  431. towrite_size += len(data)
  432. if towrite_size >= minimum_write_chunk_size:
  433. write(b''.join(towrite))
  434. towrite = []
  435. just_written_size = towrite_size
  436. towrite_size = 0
  437. if towrite:
  438. just_written_size = towrite_size
  439. write(b''.join(towrite))
  440. if not headers_sent or (use_chunked[0] and just_written_size):
  441. write(b'')
  442. except Exception:
  443. self.close_connection = 1
  444. tb = traceback.format_exc()
  445. self.server.log.info(tb)
  446. if not headers_sent:
  447. err_body = six.b(tb) if self.server.debug else b''
  448. start_response("500 Internal Server Error",
  449. [('Content-type', 'text/plain'),
  450. ('Content-length', len(err_body))])
  451. write(err_body)
  452. finally:
  453. if hasattr(result, 'close'):
  454. result.close()
  455. request_input = self.environ['eventlet.input']
  456. if (request_input.chunked_input or
  457. request_input.position < (request_input.content_length or 0)):
  458. # Read and discard body if there was no pending 100-continue
  459. if not request_input.wfile and self.close_connection == 0:
  460. try:
  461. request_input.discard()
  462. except ChunkReadError as e:
  463. self.close_connection = 1
  464. self.server.log.error((
  465. 'chunked encoding error while discarding request body.'
  466. + ' client={0} request="{1}" error="{2}"').format(
  467. self.get_client_address()[0], self.requestline, e,
  468. ))
  469. finish = time.time()
  470. for hook, args, kwargs in self.environ['eventlet.posthooks']:
  471. hook(self.environ, *args, **kwargs)
  472. if self.server.log_output:
  473. client_host, client_port = self.get_client_address()
  474. self.server.log.info(self.server.log_format % {
  475. 'client_ip': client_host,
  476. 'client_port': client_port,
  477. 'date_time': self.log_date_time_string(),
  478. 'request_line': self.requestline,
  479. 'status_code': status_code[0],
  480. 'body_length': length[0],
  481. 'wall_seconds': finish - start,
  482. })
  483. def get_client_address(self):
  484. host, port = addr_to_host_port(self.client_address)
  485. if self.server.log_x_forwarded_for:
  486. forward = self.headers.get('X-Forwarded-For', '').replace(' ', '')
  487. if forward:
  488. host = forward + ',' + host
  489. return (host, port)
  490. def get_environ(self):
  491. env = self.server.get_environ()
  492. env['REQUEST_METHOD'] = self.command
  493. env['SCRIPT_NAME'] = ''
  494. pq = self.path.split('?', 1)
  495. env['RAW_PATH_INFO'] = pq[0]
  496. env['PATH_INFO'] = urllib.parse.unquote(pq[0])
  497. if len(pq) > 1:
  498. env['QUERY_STRING'] = pq[1]
  499. ct = self.headers.get('content-type')
  500. if ct is None:
  501. try:
  502. ct = self.headers.type
  503. except AttributeError:
  504. ct = self.headers.get_content_type()
  505. env['CONTENT_TYPE'] = ct
  506. length = self.headers.get('content-length')
  507. if length:
  508. env['CONTENT_LENGTH'] = length
  509. env['SERVER_PROTOCOL'] = 'HTTP/1.0'
  510. sockname = self.request.getsockname()
  511. server_addr = addr_to_host_port(sockname)
  512. env['SERVER_NAME'] = server_addr[0]
  513. env['SERVER_PORT'] = str(server_addr[1])
  514. client_addr = addr_to_host_port(self.client_address)
  515. env['REMOTE_ADDR'] = client_addr[0]
  516. env['REMOTE_PORT'] = str(client_addr[1])
  517. env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  518. try:
  519. headers = self.headers.headers
  520. except AttributeError:
  521. headers = self.headers._headers
  522. else:
  523. headers = [h.split(':', 1) for h in headers]
  524. env['headers_raw'] = headers_raw = tuple((k, v.strip()) for k, v in headers)
  525. for k, v in headers_raw:
  526. k = k.replace('-', '_').upper()
  527. if k in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
  528. # These do not get the HTTP_ prefix and were handled above
  529. continue
  530. envk = 'HTTP_' + k
  531. if envk in env:
  532. env[envk] += ',' + v
  533. else:
  534. env[envk] = v
  535. if env.get('HTTP_EXPECT') == '100-continue':
  536. wfile = self.wfile
  537. wfile_line = b'HTTP/1.1 100 Continue\r\n'
  538. else:
  539. wfile = None
  540. wfile_line = None
  541. chunked = env.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked'
  542. env['wsgi.input'] = env['eventlet.input'] = Input(
  543. self.rfile, length, self.connection, wfile=wfile, wfile_line=wfile_line,
  544. chunked_input=chunked)
  545. env['eventlet.posthooks'] = []
  546. return env
  547. def finish(self):
  548. try:
  549. BaseHTTPServer.BaseHTTPRequestHandler.finish(self)
  550. except socket.error as e:
  551. # Broken pipe, connection reset by peer
  552. if support.get_errno(e) not in BROKEN_SOCK:
  553. raise
  554. greenio.shutdown_safe(self.connection)
  555. self.connection.close()
  556. def handle_expect_100(self):
  557. return True
  558. class Server(BaseHTTPServer.HTTPServer):
  559. def __init__(self,
  560. socket,
  561. address,
  562. app,
  563. log=None,
  564. environ=None,
  565. max_http_version=None,
  566. protocol=HttpProtocol,
  567. minimum_chunk_size=None,
  568. log_x_forwarded_for=True,
  569. keepalive=True,
  570. log_output=True,
  571. log_format=DEFAULT_LOG_FORMAT,
  572. url_length_limit=MAX_REQUEST_LINE,
  573. debug=True,
  574. socket_timeout=None,
  575. capitalize_response_headers=True):
  576. self.outstanding_requests = 0
  577. self.socket = socket
  578. self.address = address
  579. self.log = LoggerNull()
  580. if log_output:
  581. self.log = get_logger(log, debug)
  582. self.app = app
  583. self.keepalive = keepalive
  584. self.environ = environ
  585. self.max_http_version = max_http_version
  586. self.protocol = protocol
  587. self.pid = os.getpid()
  588. self.minimum_chunk_size = minimum_chunk_size
  589. self.log_x_forwarded_for = log_x_forwarded_for
  590. self.log_output = log_output
  591. self.log_format = log_format
  592. self.url_length_limit = url_length_limit
  593. self.debug = debug
  594. self.socket_timeout = socket_timeout
  595. self.capitalize_response_headers = capitalize_response_headers
  596. if not self.capitalize_response_headers:
  597. warnings.warn("""capitalize_response_headers is disabled.
  598. Please, make sure you know what you are doing.
  599. HTTP headers names are case-insensitive per RFC standard.
  600. Most likely, you need to fix HTTP parsing in your client software.""",
  601. DeprecationWarning, stacklevel=3)
  602. def get_environ(self):
  603. d = {
  604. 'wsgi.errors': sys.stderr,
  605. 'wsgi.version': (1, 0),
  606. 'wsgi.multithread': True,
  607. 'wsgi.multiprocess': False,
  608. 'wsgi.run_once': False,
  609. 'wsgi.url_scheme': 'http',
  610. }
  611. # detect secure socket
  612. if hasattr(self.socket, 'do_handshake'):
  613. d['wsgi.url_scheme'] = 'https'
  614. d['HTTPS'] = 'on'
  615. if self.environ is not None:
  616. d.update(self.environ)
  617. return d
  618. def process_request(self, sock_params):
  619. # The actual request handling takes place in __init__, so we need to
  620. # set minimum_chunk_size before __init__ executes and we don't want to modify
  621. # class variable
  622. sock, address = sock_params
  623. proto = new(self.protocol)
  624. if self.minimum_chunk_size is not None:
  625. proto.minimum_chunk_size = self.minimum_chunk_size
  626. proto.capitalize_response_headers = self.capitalize_response_headers
  627. try:
  628. proto.__init__(sock, address, self)
  629. except socket.timeout:
  630. # Expected exceptions are not exceptional
  631. sock.close()
  632. # similar to logging "accepted" in server()
  633. self.log.debug('(%s) timed out %r' % (self.pid, address))
  634. def log_message(self, message):
  635. warnings.warn('server.log_message is deprecated. Please use server.log.info instead')
  636. self.log.info(message)
  637. try:
  638. new = types.InstanceType
  639. except AttributeError:
  640. new = lambda cls: cls.__new__(cls)
  641. try:
  642. import ssl
  643. ACCEPT_EXCEPTIONS = (socket.error, ssl.SSLError)
  644. ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET,
  645. ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_SSL))
  646. except ImportError:
  647. ACCEPT_EXCEPTIONS = (socket.error,)
  648. ACCEPT_ERRNO = set((errno.EPIPE, errno.EBADF, errno.ECONNRESET))
  649. def socket_repr(sock):
  650. scheme = 'http'
  651. if hasattr(sock, 'do_handshake'):
  652. scheme = 'https'
  653. name = sock.getsockname()
  654. if sock.family == socket.AF_INET:
  655. hier_part = '//{0}:{1}'.format(*name)
  656. elif sock.family == socket.AF_INET6:
  657. hier_part = '//[{0}]:{1}'.format(*name[:2])
  658. elif sock.family == socket.AF_UNIX:
  659. hier_part = name
  660. else:
  661. hier_part = repr(name)
  662. return scheme + ':' + hier_part
  663. def server(sock, site,
  664. log=None,
  665. environ=None,
  666. max_size=None,
  667. max_http_version=DEFAULT_MAX_HTTP_VERSION,
  668. protocol=HttpProtocol,
  669. server_event=None,
  670. minimum_chunk_size=None,
  671. log_x_forwarded_for=True,
  672. custom_pool=None,
  673. keepalive=True,
  674. log_output=True,
  675. log_format=DEFAULT_LOG_FORMAT,
  676. url_length_limit=MAX_REQUEST_LINE,
  677. debug=True,
  678. socket_timeout=None,
  679. capitalize_response_headers=True):
  680. """Start up a WSGI server handling requests from the supplied server
  681. socket. This function loops forever. The *sock* object will be
  682. closed after server exits, but the underlying file descriptor will
  683. remain open, so if you have a dup() of *sock*, it will remain usable.
  684. .. warning::
  685. At the moment :func:`server` will always wait for active connections to finish before
  686. exiting, even if there's an exception raised inside it
  687. (*all* exceptions are handled the same way, including :class:`greenlet.GreenletExit`
  688. and those inheriting from `BaseException`).
  689. While this may not be an issue normally, when it comes to long running HTTP connections
  690. (like :mod:`eventlet.websocket`) it will become problematic and calling
  691. :meth:`~eventlet.greenthread.GreenThread.wait` on a thread that runs the server may hang,
  692. even after using :meth:`~eventlet.greenthread.GreenThread.kill`, as long
  693. as there are active connections.
  694. :param sock: Server socket, must be already bound to a port and listening.
  695. :param site: WSGI application function.
  696. :param log: logging.Logger instance or file-like object that logs should be written to.
  697. If a Logger instance is supplied, messages are sent to the INFO log level.
  698. If not specified, sys.stderr is used.
  699. :param environ: Additional parameters that go into the environ dictionary of every request.
  700. :param max_size: Maximum number of client connections opened at any time by this server.
  701. Default is 1024.
  702. :param max_http_version: Set to "HTTP/1.0" to make the server pretend it only supports HTTP 1.0.
  703. This can help with applications or clients that don't behave properly using HTTP 1.1.
  704. :param protocol: Protocol class. Deprecated.
  705. :param server_event: Used to collect the Server object. Deprecated.
  706. :param minimum_chunk_size: Minimum size in bytes for http chunks. This can be used to improve
  707. performance of applications which yield many small strings, though
  708. using it technically violates the WSGI spec. This can be overridden
  709. on a per request basis by setting environ['eventlet.minimum_write_chunk_size'].
  710. :param log_x_forwarded_for: If True (the default), logs the contents of the x-forwarded-for
  711. header in addition to the actual client ip address in the 'client_ip' field of the
  712. log line.
  713. :param custom_pool: A custom GreenPool instance which is used to spawn client green threads.
  714. If this is supplied, max_size is ignored.
  715. :param keepalive: If set to False, disables keepalives on the server; all connections will be
  716. closed after serving one request.
  717. :param log_output: A Boolean indicating if the server will log data or not.
  718. :param log_format: A python format string that is used as the template to generate log lines.
  719. The following values can be formatted into it: client_ip, date_time, request_line,
  720. status_code, body_length, wall_seconds. The default is a good example of how to
  721. use it.
  722. :param url_length_limit: A maximum allowed length of the request url. If exceeded, 414 error
  723. is returned.
  724. :param debug: True if the server should send exception tracebacks to the clients on 500 errors.
  725. If False, the server will respond with empty bodies.
  726. :param socket_timeout: Timeout for client connections' socket operations. Default None means
  727. wait forever.
  728. :param capitalize_response_headers: Normalize response headers' names to Foo-Bar.
  729. Default is True.
  730. """
  731. serv = Server(sock, sock.getsockname(),
  732. site, log,
  733. environ=environ,
  734. max_http_version=max_http_version,
  735. protocol=protocol,
  736. minimum_chunk_size=minimum_chunk_size,
  737. log_x_forwarded_for=log_x_forwarded_for,
  738. keepalive=keepalive,
  739. log_output=log_output,
  740. log_format=log_format,
  741. url_length_limit=url_length_limit,
  742. debug=debug,
  743. socket_timeout=socket_timeout,
  744. capitalize_response_headers=capitalize_response_headers,
  745. )
  746. if server_event is not None:
  747. server_event.send(serv)
  748. if max_size is None:
  749. max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS
  750. if custom_pool is not None:
  751. pool = custom_pool
  752. else:
  753. pool = greenpool.GreenPool(max_size)
  754. try:
  755. serv.log.info("(%s) wsgi starting up on %s" % (
  756. serv.pid, socket_repr(sock)))
  757. while is_accepting:
  758. try:
  759. client_socket = sock.accept()
  760. client_socket[0].settimeout(serv.socket_timeout)
  761. serv.log.debug("(%s) accepted %r" % (
  762. serv.pid, client_socket[1]))
  763. try:
  764. pool.spawn_n(serv.process_request, client_socket)
  765. except AttributeError:
  766. warnings.warn("wsgi's pool should be an instance of "
  767. "eventlet.greenpool.GreenPool, is %s. Please convert your"
  768. " call site to use GreenPool instead" % type(pool),
  769. DeprecationWarning, stacklevel=2)
  770. pool.execute_async(serv.process_request, client_socket)
  771. except ACCEPT_EXCEPTIONS as e:
  772. if support.get_errno(e) not in ACCEPT_ERRNO:
  773. raise
  774. except (KeyboardInterrupt, SystemExit):
  775. serv.log.info("wsgi exiting")
  776. break
  777. finally:
  778. pool.waitall()
  779. serv.log.info("(%s) wsgi exited, is_accepting=%s" % (
  780. serv.pid, is_accepting))
  781. try:
  782. # NOTE: It's not clear whether we want this to leave the
  783. # socket open or close it. Use cases like Spawning want
  784. # the underlying fd to remain open, but if we're going
  785. # that far we might as well not bother closing sock at
  786. # all.
  787. sock.close()
  788. except socket.error as e:
  789. if support.get_errno(e) not in BROKEN_SOCK:
  790. traceback.print_exc()