PageRenderTime 36ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/examples/networking/wsgi/stacklesswsgi.py

http://stacklessexamples.googlecode.com/
Python | 848 lines | 680 code | 43 blank | 125 comment | 53 complexity | 4fde31581eaedbe12d70d85f5d7cb664 MD5 | raw file
  1. #
  2. # An example which implements a WSGI compliant HTTP server using asyncore
  3. # networking. Each HTTP connection is dispatched to a new tasklet for
  4. # processing.
  5. #
  6. # Author: Arnar Birgisson <arnarbi@gmail.com>
  7. #
  8. # Code implementing the HTTP RFCs by Robert Brewer and the CherryPy team,
  9. # see disclaimer below.
  10. #
  11. # This code was written to serve as an example of Stackless Python usage.
  12. # Feel free to email me with any questions, comments, or suggestions for
  13. # improvement.
  14. #
  15. # But a better place to discuss Stackless Python related matters is the
  16. # mailing list:
  17. #
  18. # http://www.tismer.com/mailman/listinfo/stackless
  19. #
  20. import re
  21. import rfc822
  22. import sys
  23. import traceback
  24. from urllib import unquote
  25. from urlparse import urlparse
  26. import asyncore
  27. import socket
  28. import stackless
  29. class Server(object):
  30. """A WSGI complient web server.
  31. A usage example:
  32. >>> s = Server((127.0.0.1, 8080), wsgi_app_callable)
  33. >>> s.start()
  34. This will start a server listening on 127.0.0.1 port 8080.
  35. It will also start the stackless scheduler and begin serving
  36. requests.
  37. """
  38. protocol = "HTTP/1.1"
  39. version = "stacklesswsgi/0.1alpha"
  40. ready = False
  41. environ = {}
  42. def __init__(self, bind_addr, wsgi_app, server_name=None):
  43. """Instantiate a WSGI server.
  44. - bind_addr is a (hostname,port) tuple
  45. - wsgi_app is a callable application as per the WSGI spec
  46. - server_name is the server name, defaulting to the local hostname
  47. """
  48. self.bind_addr = bind_addr
  49. self.wsgi_app = wsgi_app
  50. if not server_name:
  51. server_name = socket.gethostname()
  52. self.server_name = server_name
  53. self.connection_class = HTTPConnection
  54. self.tasklet_class = stackless.tasklet
  55. self.running = False
  56. def start(self, start_stackless=True):
  57. """Start serving HTTP requests on bound port. If start_stackless
  58. is True (default) this will call stackless.run() and block. Set it
  59. to False if you intend to start the stackless processing loop yourself or
  60. call stackless.schedule by some other means. Otherwise the server will
  61. not function since all work is deferred to a tasklet.
  62. """
  63. self.sock_server = sock_server(self.bind_addr)
  64. self.running = True
  65. self.tasklet_class(self._accept_loop)()
  66. if start_stackless:
  67. stackless.run()
  68. def stop(self):
  69. """Call this to make the server stop serving requests. If it is serving
  70. a request at the time stop() is called, it will finish that and then stop."""
  71. self.running = False
  72. def _accept_loop(self):
  73. """The main loop of the server, run in a seperate tasklet by start()."""
  74. while self.running:
  75. # This line will suspend the server tasklet until there is a connection
  76. s, addr = self.sock_server.accept()
  77. # See if we have already been asked to stop
  78. if not self.running:
  79. return
  80. # Initialize the WSGI environment
  81. environ = self.environ.copy()
  82. environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
  83. environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol
  84. environ["SERVER_NAME"] = self.server_name
  85. environ["SERVER_PORT"] = str(self.bind_addr[1])
  86. environ["REMOTE_ADDR"] = addr[0]
  87. environ["REMOTE_PORT"] = str(addr[1])
  88. # self.connection_class is a reference to a class that will
  89. # take care of reading and parsing requests out of the connection
  90. conn = self.connection_class(s, self.wsgi_app, environ)
  91. # We create a new tasklet for each connection. This is similar
  92. # to how threaded web servers work, except they usually keep a thread
  93. # pool with an upper limit on number of threads. We just create new tasklets
  94. # blindly without regard for how many requests the server is serving
  95. # already. This is possible because of the light-weight nature of
  96. # tasklets compared to threads.
  97. def comm(connection):
  98. try:
  99. connection.communicate()
  100. finally:
  101. connection.close()
  102. self.tasklet_class(comm)(conn)
  103. # This method is intended to be started in a seperate tasklet.
  104. # It will repeatedly call asyncore.poll() to dispatch asyncore events
  105. # to the relevant listeners.
  106. # This is started by sock_server, which is in turn started by the server above.
  107. # It is the responsibility of the caller not to invoke this if it's already
  108. # running. Callers can check the asyncore_loop.running attribute to see
  109. # if another invocation is active.
  110. def asyncore_loop():
  111. # Make sure only one invocation is active at any time
  112. assert asyncore_loop.running == False
  113. asyncore_loop.running = True
  114. try:
  115. while len(asyncore.socket_map):
  116. asyncore.poll(0.05)
  117. stackless.schedule()
  118. finally:
  119. asyncore_loop.running = False
  120. asyncore_loop.running = False
  121. class sock_server(asyncore.dispatcher):
  122. """This is an asyncore.dispatcher that listens on a TCP port. For each
  123. incoming connection, a sock_channel dispatcher is created and given
  124. responsibility over the socket"""
  125. def __init__(self, addr):
  126. """Bind to addr and start listening"""
  127. asyncore.dispatcher.__init__(self)
  128. self.accept_channel = None
  129. self.addr = addr
  130. self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
  131. self.bind(addr)
  132. self.listen(5)
  133. # Start the asyncore polling loop if it's not already running
  134. if not asyncore_loop.running:
  135. stackless.tasklet(asyncore_loop)()
  136. def accept(self):
  137. # This will suspend the current tasklet (by reading from
  138. # self.accept_channel). See handle_accept for details on
  139. # when the tasklet is resumed.
  140. if self.accept_channel is None:
  141. self.accept_channel = stackless.channel()
  142. return self.accept_channel.receive()
  143. def handle_accept(self):
  144. # This is called by asyncore to signal that a socket is ready for
  145. # accept on the listening port. We see if any calls to self.accept()
  146. # are waiting on self.accept_channel. If so, we accept the socket
  147. # and write the results on the channel and the tasklet that called
  148. # accept() is resumed.
  149. if self.accept_channel and self.accept_channel.balance < 0:
  150. s, a = asyncore.dispatcher.accept(self)
  151. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  152. s = sock_channel(s)
  153. self.accept_channel.send((s,a))
  154. class sock_channel(asyncore.dispatcher):
  155. """This is an asyncore dispatcher in charge of handling connections
  156. to http clients."""
  157. def __init__(self, sock):
  158. """Initialize and start handling the connection on sock. Usually called
  159. by sock_server"""
  160. if sock.type == socket.SOCK_DGRAM:
  161. raise NotImplementedError("sock_channel can only handle TCP sockets")
  162. asyncore.dispatcher.__init__(self, sock)
  163. self.send_buffer = ""
  164. self.recv_buffer = ""
  165. self.sendall_channel = None
  166. self.recv_channel = stackless.channel()
  167. def writable(self):
  168. if not self.connected:
  169. return True
  170. # If we have buffered data to send, we're intersted in write events
  171. return len(self.send_buffer)
  172. def send(self, data):
  173. if self.send_buffer is None:
  174. raise socket.error(socket.EBADF, "Connection closed")
  175. self.send_buffer += data
  176. # Request a schedule so that asyncore get's a chance to invoke the
  177. # handle_write event. There is no guarantee that the data will have
  178. # been sent completely when we return to here again.
  179. stackless.schedule()
  180. return len(data)
  181. def sendall(self, data):
  182. if self.send_buffer is None:
  183. raise socket.error(socket.EBADF, "Connection closed")
  184. self.send_buffer += data
  185. # Instead of asking for a schedule like send() does, we suspend
  186. # the current tasklet by reading from self.sendall_channel. Only
  187. # when the send_buffer has been completely sent on the wire, this
  188. # call is resumed.
  189. if self.sendall_channel is None:
  190. self.sendall_channel = stackless.channel()
  191. self.sendall_channel.receive()
  192. # Here we are guaranteed that all of data has been sent
  193. return len(data)
  194. def handle_write(self):
  195. # This is called by asyncore when it is ready to send out data.
  196. # First see if we have some data to send...
  197. if len(self.send_buffer):
  198. sent = asyncore.dispatcher.send(self, self.send_buffer[:512])
  199. self.send_buffer = self.send_buffer[sent:]
  200. # If we completed sending everything in self.send_buffer and a call to
  201. # sendall is waiting in another tasklet, let it know so it can resume.
  202. if len(self.send_buffer) == 0 and self.sendall_channel and self.sendall_channel.balance < 0:
  203. self.sendall_channel.send(None)
  204. def recv(self, byte_count):
  205. # A call to this method will suspend the current tasklet until there is
  206. # data available to be received. See handle_read on how that happens.
  207. if byte_count > 0 and len(self.recv_buffer) == 0 or self.recv_channel.balance > 0:
  208. self.recv_buffer += self.recv_channel.receive()
  209. # Give the caller only as much as he asked for
  210. ret = self.recv_buffer[:byte_count]
  211. # and stash the rest in self.recv_buffer
  212. self.recv_buffer = self.recv_buffer[byte_count:]
  213. return ret
  214. def handle_read(self):
  215. # This is called by asyncore to let us know that there is data available
  216. # to be received.
  217. try:
  218. ret = asyncore.dispatcher.recv(self, 20000)
  219. if not ret:
  220. # This means the other end closed the connection, close our end.
  221. self.close()
  222. # Send the data to whoever is or will be calling recv()
  223. self.recv_channel.send(ret)
  224. except socket.error, err:
  225. if self.send_buffer:
  226. self.send_buffer = ""
  227. # Any errors on the socket is propogated to the callers of recv()
  228. self.recv_channel.send_exception(stdsocket.error, err)
  229. def close(self):
  230. asyncore.dispatcher.close(self)
  231. self.connected = False
  232. self.accepting = False
  233. self.send_buffer = None
  234. # Wake any tasklets that are waiting for sendall() to return
  235. if self.sendall_channel and self.sendall_channel.balance < 0:
  236. self.sendall_channel.send(None)
  237. # Wake any tasklets that are waiting for recv to return
  238. while self.recv_channel and self.recv_channel.balance < 0:
  239. self.recv_channel.send("")
  240. def handle_close(self):
  241. pass
  242. def handle_expt(self):
  243. self.close()
  244. class sock_channel_rfile(object):
  245. """This class provides a read-only file-like object on top of sock_channel.
  246. It is used by the HTTPRequest class to get data from the connection and
  247. allow WSGI apps to read the request body."""
  248. def __init__(self, sock_chan):
  249. self.sock_chan = sock_chan
  250. self.buffer = ""
  251. def read(self, size=-1):
  252. if size == -1:
  253. data = self.sock_chan.recv(512)
  254. while len(data):
  255. self.buffer += data
  256. data = self.sock_chan.recv(512)
  257. data = self.buffer
  258. self.buffer = ""
  259. return data
  260. else:
  261. data = self.sock_chan.recv(size)
  262. retval = self.buffer + data
  263. self.buffer = ""
  264. return retval
  265. def readline(self):
  266. idx = self.buffer.find("\n")
  267. if idx > -1:
  268. line = self.buffer[:idx+1]
  269. self.buffer = self.buffer[idx+1:]
  270. return line
  271. while "\n" not in self.buffer:
  272. chunk = self.read(512)
  273. if chunk == "":
  274. break
  275. self.buffer += chunk
  276. if self.buffer == "":
  277. return ""
  278. idx = self.buffer.find("\n")
  279. if idx > -1:
  280. line = self.buffer[:idx+1]
  281. self.buffer = self.buffer[idx+1:]
  282. return line
  283. else:
  284. line = self.buffer
  285. self.buffer = ""
  286. return line
  287. def readlines(self, hint=None):
  288. lines = []
  289. line = self.readline()
  290. while line:
  291. lines.append(line)
  292. line = self.readline()
  293. return lines
  294. def __iter__(self):
  295. return self
  296. def next(self):
  297. line = self.readline()
  298. if line:
  299. return line
  300. else:
  301. raise StopIteration
  302. def close(self):
  303. self.sock_chan = None
  304. self.buffer = ""
  305. # The rest of this file is taken from CherryPy's excellent WSGI Server by Robert
  306. # Brewer. CherryPy is distributed under the BSD license.
  307. # See http://www.cherrypy.org for more information. I have only removed parts
  308. # relevant to SSL support and added one call to stackless.schedule.
  309. # Otherwise, this code is:
  310. #
  311. # Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
  312. # All rights reserved.
  313. quoted_slash = re.compile("(?i)%2F")
  314. import errno
  315. socket_errors_to_ignore = set(_ for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET",
  316. "EHOSTDOWN", "EHOSTUNREACH",
  317. "WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET",
  318. "WSAENETRESET", "WSAETIMEDOUT") if _ in dir(errno))
  319. socket_errors_to_ignore.add("timed out")
  320. comma_separated_headers = set(['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
  321. 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
  322. 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
  323. 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
  324. 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
  325. 'WWW-AUTHENTICATE'])
  326. class HTTPRequest(object):
  327. """An HTTP Request (and response).
  328. A single HTTP connection may consist of multiple request/response pairs.
  329. sendall: the 'sendall' method from the connection's fileobject.
  330. wsgi_app: the WSGI application to call.
  331. environ: a partial WSGI environ (server and connection entries).
  332. The caller MUST set the following entries:
  333. * All wsgi.* entries, including .input
  334. * SERVER_NAME and SERVER_PORT
  335. * Any SSL_* entries
  336. * Any custom entries like REMOTE_ADDR and REMOTE_PORT
  337. * SERVER_SOFTWARE: the value to write in the "Server" response header.
  338. * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of
  339. the response. From RFC 2145: "An HTTP server SHOULD send a
  340. response version equal to the highest version for which the
  341. server is at least conditionally compliant, and whose major
  342. version is less than or equal to the one received in the
  343. request. An HTTP server MUST NOT send a version for which
  344. it is not at least conditionally compliant."
  345. outheaders: a list of header tuples to write in the response.
  346. ready: when True, the request has been parsed and is ready to begin
  347. generating the response. When False, signals the calling Connection
  348. that the response should not be generated and the connection should
  349. close.
  350. close_connection: signals the calling Connection that the request
  351. should close. This does not imply an error! The client and/or
  352. server may each request that the connection be closed.
  353. chunked_write: if True, output will be encoded with the "chunked"
  354. transfer-coding. This value is set automatically inside
  355. send_headers.
  356. """
  357. def __init__(self, sendall, environ, wsgi_app):
  358. self.rfile = environ['wsgi.input']
  359. self.sendall = sendall
  360. self.environ = environ.copy()
  361. self.wsgi_app = wsgi_app
  362. self.ready = False
  363. self.started_response = False
  364. self.status = ""
  365. self.outheaders = []
  366. self.sent_headers = False
  367. self.close_connection = False
  368. self.chunked_write = False
  369. def parse_request(self):
  370. """Parse the next HTTP request start-line and message-headers."""
  371. # HTTP/1.1 connections are persistent by default. If a client
  372. # requests a page, then idles (leaves the connection open),
  373. # then rfile.readline() will raise socket.error("timed out").
  374. # Note that it does this based on the value given to settimeout(),
  375. # and doesn't need the client to request or acknowledge the close
  376. # (although your TCP stack might suffer for it: cf Apache's history
  377. # with FIN_WAIT_2).
  378. request_line = self.rfile.readline()
  379. if not request_line:
  380. # Force self.ready = False so the connection will close.
  381. self.ready = False
  382. return
  383. if request_line == "\r\n":
  384. # RFC 2616 sec 4.1: "...if the server is reading the protocol
  385. # stream at the beginning of a message and receives a CRLF
  386. # first, it should ignore the CRLF."
  387. # But only ignore one leading line! else we enable a DoS.
  388. request_line = self.rfile.readline()
  389. if not request_line:
  390. self.ready = False
  391. return
  392. environ = self.environ
  393. method, path, req_protocol = request_line.strip().split(" ", 2)
  394. environ["REQUEST_METHOD"] = method
  395. # path may be an abs_path (including "http://host.domain.tld");
  396. scheme, location, path, params, qs, frag = urlparse(path)
  397. if frag:
  398. self.simple_response("400 Bad Request",
  399. "Illegal #fragment in Request-URI.")
  400. return
  401. if scheme:
  402. environ["wsgi.url_scheme"] = scheme
  403. if params:
  404. path = path + ";" + params
  405. environ["SCRIPT_NAME"] = ""
  406. # Unquote the path+params (e.g. "/this%20path" -> "this path").
  407. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
  408. #
  409. # But note that "...a URI must be separated into its components
  410. # before the escaped characters within those components can be
  411. # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
  412. atoms = [unquote(x) for x in quoted_slash.split(path)]
  413. path = "%2F".join(atoms)
  414. environ["PATH_INFO"] = path
  415. # Note that, like wsgiref and most other WSGI servers,
  416. # we unquote the path but not the query string.
  417. environ["QUERY_STRING"] = qs
  418. # Compare request and server HTTP protocol versions, in case our
  419. # server does not support the requested protocol. Limit our output
  420. # to min(req, server). We want the following output:
  421. # request server actual written supported response
  422. # protocol protocol response protocol feature set
  423. # a 1.0 1.0 1.0 1.0
  424. # b 1.0 1.1 1.1 1.0
  425. # c 1.1 1.0 1.0 1.0
  426. # d 1.1 1.1 1.1 1.1
  427. # Notice that, in (b), the response will be "HTTP/1.1" even though
  428. # the client only understands 1.0. RFC 2616 10.5.6 says we should
  429. # only return 505 if the _major_ version is different.
  430. rp = int(req_protocol[5]), int(req_protocol[7])
  431. server_protocol = environ["ACTUAL_SERVER_PROTOCOL"]
  432. sp = int(server_protocol[5]), int(server_protocol[7])
  433. if sp[0] != rp[0]:
  434. self.simple_response("505 HTTP Version Not Supported")
  435. return
  436. # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
  437. environ["SERVER_PROTOCOL"] = req_protocol
  438. self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
  439. # If the Request-URI was an absoluteURI, use its location atom.
  440. if location:
  441. environ["SERVER_NAME"] = location
  442. # then all the http headers
  443. try:
  444. self.read_headers()
  445. except ValueError, ex:
  446. self.simple_response("400 Bad Request", repr(ex.args))
  447. return
  448. creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1)
  449. environ["AUTH_TYPE"] = creds[0]
  450. if creds[0].lower() == 'basic':
  451. user, pw = base64.decodestring(creds[1]).split(":", 1)
  452. environ["REMOTE_USER"] = user
  453. # Persistent connection support
  454. if self.response_protocol == "HTTP/1.1":
  455. if environ.get("HTTP_CONNECTION", "") == "close":
  456. self.close_connection = True
  457. else:
  458. # HTTP/1.0
  459. if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
  460. self.close_connection = True
  461. # Transfer-Encoding support
  462. te = None
  463. if self.response_protocol == "HTTP/1.1":
  464. te = environ.get("HTTP_TRANSFER_ENCODING")
  465. if te:
  466. te = [x.strip().lower() for x in te.split(",") if x.strip()]
  467. read_chunked = False
  468. if te:
  469. for enc in te:
  470. if enc == "chunked":
  471. read_chunked = True
  472. else:
  473. # Note that, even if we see "chunked", we must reject
  474. # if there is an extension we don't recognize.
  475. self.simple_response("501 Unimplemented")
  476. self.close_connection = True
  477. return
  478. if read_chunked:
  479. if not self.decode_chunked():
  480. return
  481. # From PEP 333:
  482. # "Servers and gateways that implement HTTP 1.1 must provide
  483. # transparent support for HTTP 1.1's "expect/continue" mechanism.
  484. # This may be done in any of several ways:
  485. # 1. Respond to requests containing an Expect: 100-continue request
  486. # with an immediate "100 Continue" response, and proceed normally.
  487. # 2. Proceed with the request normally, but provide the application
  488. # with a wsgi.input stream that will send the "100 Continue"
  489. # response if/when the application first attempts to read from
  490. # the input stream. The read request must then remain blocked
  491. # until the client responds.
  492. # 3. Wait until the client decides that the server does not support
  493. # expect/continue, and sends the request body on its own.
  494. # (This is suboptimal, and is not recommended.)
  495. #
  496. # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
  497. # but it seems like it would be a big slowdown for such a rare case.
  498. if environ.get("HTTP_EXPECT", "") == "100-continue":
  499. self.simple_response(100)
  500. self.ready = True
  501. def read_headers(self):
  502. """Read header lines from the incoming stream."""
  503. environ = self.environ
  504. while True:
  505. line = self.rfile.readline()
  506. if not line:
  507. # No more data--illegal end of headers
  508. raise ValueError("Illegal end of headers.")
  509. if line == '\r\n':
  510. # Normal end of headers
  511. break
  512. if line[0] in ' \t':
  513. # It's a continuation line.
  514. v = line.strip()
  515. else:
  516. k, v = line.split(":", 1)
  517. k, v = k.strip().upper(), v.strip()
  518. envname = "HTTP_" + k.replace("-", "_")
  519. if k in comma_separated_headers:
  520. existing = environ.get(envname)
  521. if existing:
  522. v = ", ".join((existing, v))
  523. environ[envname] = v
  524. ct = environ.pop("HTTP_CONTENT_TYPE", None)
  525. if ct:
  526. environ["CONTENT_TYPE"] = ct
  527. cl = environ.pop("HTTP_CONTENT_LENGTH", None)
  528. if cl:
  529. environ["CONTENT_LENGTH"] = cl
  530. def decode_chunked(self):
  531. """Decode the 'chunked' transfer coding."""
  532. cl = 0
  533. data = StringIO.StringIO()
  534. while True:
  535. line = self.rfile.readline().strip().split(";", 1)
  536. chunk_size = int(line.pop(0), 16)
  537. if chunk_size <= 0:
  538. break
  539. cl += chunk_size
  540. data.write(self.rfile.read(chunk_size))
  541. crlf = self.rfile.read(2)
  542. if crlf != "\r\n":
  543. self.simple_response("400 Bad Request",
  544. "Bad chunked transfer coding "
  545. "(expected '\\r\\n', got %r)" % crlf)
  546. return
  547. # Grab any trailer headers
  548. self.read_headers()
  549. data.seek(0)
  550. self.environ["wsgi.input"] = data
  551. self.environ["CONTENT_LENGTH"] = str(cl) or ""
  552. return True
  553. def respond(self):
  554. """Call the appropriate WSGI app and write its iterable output."""
  555. response = self.wsgi_app(self.environ, self.start_response)
  556. try:
  557. for chunk in response:
  558. # "The start_response callable must not actually transmit
  559. # the response headers. Instead, it must store them for the
  560. # server or gateway to transmit only after the first
  561. # iteration of the application return value that yields
  562. # a NON-EMPTY string, or upon the application's first
  563. # invocation of the write() callable." (PEP 333)
  564. if chunk:
  565. self.write(chunk)
  566. stackless.schedule()
  567. finally:
  568. if hasattr(response, "close"):
  569. response.close()
  570. if (self.ready and not self.sent_headers):
  571. self.sent_headers = True
  572. self.send_headers()
  573. if self.chunked_write:
  574. self.sendall("0\r\n\r\n")
  575. def simple_response(self, status, msg=""):
  576. """Write a simple response back to the client."""
  577. status = str(status)
  578. buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status),
  579. "Content-Length: %s\r\n" % len(msg)]
  580. if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
  581. # Request Entity Too Large
  582. self.close_connection = True
  583. buf.append("Connection: close\r\n")
  584. buf.append("\r\n")
  585. if msg:
  586. buf.append(msg)
  587. self.sendall("".join(buf))
  588. def start_response(self, status, headers, exc_info = None):
  589. """WSGI callable to begin the HTTP response."""
  590. if self.started_response:
  591. if not exc_info:
  592. raise AssertionError("WSGI start_response called a second "
  593. "time with no exc_info.")
  594. else:
  595. try:
  596. raise exc_info[0], exc_info[1], exc_info[2]
  597. finally:
  598. exc_info = None
  599. self.started_response = True
  600. self.status = status
  601. self.outheaders.extend(headers)
  602. return self.write
  603. def write(self, chunk):
  604. """WSGI callable to write unbuffered data to the client.
  605. This method is also used internally by start_response (to write
  606. data from the iterable returned by the WSGI application).
  607. """
  608. if not self.started_response:
  609. raise AssertionError("WSGI write called before start_response.")
  610. if not self.sent_headers:
  611. self.sent_headers = True
  612. self.send_headers()
  613. if self.chunked_write and chunk:
  614. buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
  615. self.sendall("".join(buf))
  616. else:
  617. self.sendall(chunk)
  618. def send_headers(self):
  619. """Assert, process, and send the HTTP response message-headers."""
  620. hkeys = [key.lower() for key, value in self.outheaders]
  621. status = int(self.status[:3])
  622. if status == 413:
  623. # Request Entity Too Large. Close conn to avoid garbage.
  624. self.close_connection = True
  625. elif "content-length" not in hkeys:
  626. # "All 1xx (informational), 204 (no content),
  627. # and 304 (not modified) responses MUST NOT
  628. # include a message-body." So no point chunking.
  629. if status < 200 or status in (204, 205, 304):
  630. pass
  631. else:
  632. if self.response_protocol == 'HTTP/1.1':
  633. # Use the chunked transfer-coding
  634. self.chunked_write = True
  635. self.outheaders.append(("Transfer-Encoding", "chunked"))
  636. else:
  637. # Closing the conn is the only way to determine len.
  638. self.close_connection = True
  639. if "connection" not in hkeys:
  640. if self.response_protocol == 'HTTP/1.1':
  641. if self.close_connection:
  642. self.outheaders.append(("Connection", "close"))
  643. else:
  644. if not self.close_connection:
  645. self.outheaders.append(("Connection", "Keep-Alive"))
  646. if "date" not in hkeys:
  647. self.outheaders.append(("Date", rfc822.formatdate()))
  648. if "server" not in hkeys:
  649. self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE']))
  650. buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"]
  651. try:
  652. buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
  653. except TypeError:
  654. if not isinstance(k, str):
  655. raise TypeError("WSGI response header key %r is not a string.")
  656. if not isinstance(v, str):
  657. raise TypeError("WSGI response header value %r is not a string.")
  658. else:
  659. raise
  660. buf.append("\r\n")
  661. self.sendall("".join(buf))
  662. class HTTPConnection(object):
  663. """An HTTP connection (active socket).
  664. sock_chan: the sock_channel object for this connection.
  665. wsgi_app: the WSGI application for this server/connection.
  666. environ: a WSGI environ template. This will be copied for each request.
  667. rfile: a fileobject for reading from the sock_chan.
  668. sendall: a function for writing (+ flush) to the sock_chan.
  669. """
  670. RequestHandlerClass = HTTPRequest
  671. environ = {"wsgi.version": (1, 0),
  672. "wsgi.url_scheme": "http",
  673. "wsgi.multithread": True,
  674. "wsgi.multiprocess": False,
  675. "wsgi.run_once": False,
  676. "wsgi.errors": sys.stderr,
  677. }
  678. def __init__(self, sock_chan, wsgi_app, environ):
  679. self.sock_chan = sock_chan
  680. self.wsgi_app = wsgi_app
  681. # Copy the class environ into self.
  682. self.environ = self.environ.copy()
  683. self.environ.update(environ)
  684. self.rfile = sock_channel_rfile(sock_chan)
  685. self.sendall = sock_chan.sendall
  686. self.environ["wsgi.input"] = self.rfile
  687. def communicate(self):
  688. """Read each request and respond appropriately."""
  689. try:
  690. while True:
  691. # (re)set req to None so that if something goes wrong in
  692. # the RequestHandlerClass constructor, the error doesn't
  693. # get written to the previous request.
  694. req = None
  695. req = self.RequestHandlerClass(self.sendall, self.environ,
  696. self.wsgi_app)
  697. # This order of operations should guarantee correct pipelining.
  698. req.parse_request()
  699. if not req.ready:
  700. return
  701. req.respond()
  702. if req.close_connection:
  703. return
  704. except socket.error, e:
  705. errno = e.args[0]
  706. if errno not in socket_errors_to_ignore:
  707. if req:
  708. req.simple_response("500 Internal Server Error",
  709. traceback.format_exc())
  710. return
  711. except (KeyboardInterrupt, SystemExit):
  712. raise
  713. except:
  714. if req:
  715. try:
  716. req.simple_response("500 Internal Server Error", traceback.format_exc())
  717. except socket.error, e:
  718. if e.args[0] != socket.EBADF:
  719. raise
  720. # Otherwise, connection was closed and we have nowhere to print error
  721. def close(self):
  722. """Close the socket underlying this connection."""
  723. self.rfile.close()
  724. self.sock_chan.close()