HTTPHandler.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.1 (the License). You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License. You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10. # Written by Bram Cohen
  11. import datetime
  12. from RawServer_twisted import Handler
  13. from cStringIO import StringIO
  14. from sys import stdout
  15. import time
  16. from gzip import GzipFile
  17. from BTL.translation import _
  18. DEBUG = False
  19. class HTTPConnector(object):
  20. def __init__(self, handler, connection):
  21. self.handler = handler
  22. self.connection = connection
  23. self.buf = ''
  24. self.closed = False
  25. self.done = False
  26. self.donereading = False
  27. self.next_func = self.read_type
  28. def get_ip(self):
  29. return self.connection.ip
  30. def data_came_in(self, data):
  31. if self.donereading or self.next_func is None:
  32. return True
  33. self.buf += data
  34. while True:
  35. try:
  36. i = self.buf.index('\n')
  37. except ValueError:
  38. return True
  39. val = self.buf[:i]
  40. self.buf = self.buf[i+1:]
  41. self.next_func = self.next_func(val)
  42. if self.donereading:
  43. return True
  44. if self.next_func is None or self.closed:
  45. return False
  46. def read_type(self, data):
  47. self.header = data.strip()
  48. words = data.split()
  49. if len(words) == 3:
  50. self.command, self.path, garbage = words
  51. self.pre1 = False
  52. elif len(words) == 2:
  53. self.command, self.path = words
  54. self.pre1 = True
  55. if self.command != 'GET':
  56. return None
  57. else:
  58. return None
  59. if self.command not in ('HEAD', 'GET'):
  60. return None
  61. self.headers = {}
  62. return self.read_header
  63. def read_header(self, data):
  64. data = data.strip()
  65. if data == '':
  66. self.donereading = True
  67. # check for Accept-Encoding: header, pick a
  68. if self.headers.has_key('accept-encoding'):
  69. ae = self.headers['accept-encoding']
  70. if DEBUG:
  71. print "Got Accept-Encoding: " + ae + "\n"
  72. else:
  73. #identity assumed if no header
  74. ae = 'identity'
  75. # this eventually needs to support multple acceptable types
  76. # q-values and all that fancy HTTP crap
  77. # for now assume we're only communicating with our own client
  78. if ae.find('gzip') != -1:
  79. self.encoding = 'gzip'
  80. else:
  81. #default to identity.
  82. self.encoding = 'identity'
  83. r = self.handler.getfunc(self, self.path, self.headers)
  84. if r is not None:
  85. self.answer(r)
  86. return None
  87. try:
  88. i = data.index(':')
  89. except ValueError:
  90. return None
  91. self.headers[data[:i].strip().lower()] = data[i+1:].strip()
  92. if DEBUG:
  93. print data[:i].strip() + ": " + data[i+1:].strip()
  94. return self.read_header
  95. def answer(self, (responsecode, responsestring, headers, data)):
  96. if self.closed:
  97. return
  98. if self.encoding == 'gzip':
  99. #transform data using gzip compression
  100. #this is nasty but i'm unsure of a better way at the moment
  101. compressed = StringIO()
  102. gz = GzipFile(fileobj = compressed, mode = 'wb', compresslevel = 9)
  103. gz.write(data)
  104. gz.close()
  105. compressed.seek(0,0)
  106. cdata = compressed.read()
  107. compressed.close()
  108. if len(cdata) >= len(data):
  109. self.encoding = 'identity'
  110. else:
  111. if DEBUG:
  112. print _("Compressed: %i Uncompressed: %i\n") % (len(cdata),len(data))
  113. data = cdata
  114. headers['Content-Encoding'] = 'gzip'
  115. # i'm abusing the identd field here, but this should be ok
  116. if self.encoding == 'identity':
  117. ident = '-'
  118. else:
  119. ident = self.encoding
  120. username = '-'
  121. referer = self.headers.get('referer','-')
  122. useragent = self.headers.get('user-agent','-')
  123. timestamp = datetime.datetime.utcnow().isoformat()
  124. if DEBUG:
  125. print '%s %s %s [%s] "%s" %i %i "%s" "%s"' % (
  126. self.connection.ip, ident, username, timestamp, self.header,
  127. responsecode, len(data), referer, useragent)
  128. t = time.time()
  129. if t - self.handler.lastflush > self.handler.minflush:
  130. self.handler.lastflush = t
  131. stdout.flush()
  132. self.done = True
  133. r = StringIO()
  134. r.write('HTTP/1.0 ' + str(responsecode) + ' ' +
  135. responsestring + '\r\n')
  136. if not self.pre1:
  137. headers['Content-Length'] = len(data)
  138. for key, value in headers.items():
  139. r.write(key + ': ' + str(value) + '\r\n')
  140. r.write('\r\n')
  141. if self.command != 'HEAD':
  142. r.write(data)
  143. self.connection.write(r.getvalue())
  144. if self.connection.is_flushed():
  145. self.connection.shutdown(1)
  146. class HTTPHandler(Handler):
  147. def __init__(self, getfunc, minflush):
  148. self.connections = {}
  149. self.getfunc = getfunc
  150. self.minflush = minflush
  151. self.lastflush = time.time()
  152. def connection_made(self, connection):
  153. if DEBUG:
  154. print "HTTPHandler.connection_made"
  155. self.connections[connection] = HTTPConnector(self, connection)
  156. def connection_flushed(self, connection):
  157. if self.connections[connection].done:
  158. connection.shutdown(1)
  159. def connection_lost(self, connection):
  160. ec = self.connections[connection]
  161. ec.closed = True
  162. del ec.connection
  163. del ec.next_func
  164. del self.connections[connection]
  165. def data_came_in(self, connection, data):
  166. if DEBUG:
  167. print "HTTPHandler.data_came_in: '%s'" % data
  168. c = self.connections[connection]
  169. if not c.data_came_in(data) and not c.closed:
  170. c.connection.shutdown(1)