A fork of hyde, the static site generation. Some patches will be pushed upstream.
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.
 
 
 

234 lines
8.1 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. Contains classes and utilities for serving a site
  4. generated from hyde.
  5. """
  6. import os
  7. import select
  8. import threading
  9. import urlparse
  10. import urllib
  11. from datetime import datetime
  12. from SimpleHTTPServer import SimpleHTTPRequestHandler
  13. from BaseHTTPServer import HTTPServer
  14. from hyde.fs import File, Folder
  15. from hyde.site import Site
  16. from hyde.generator import Generator
  17. from hyde.exceptions import HydeException
  18. from hyde.util import getLoggerWithNullHandler
  19. logger = getLoggerWithNullHandler('hyde.server')
  20. class HydeRequestHandler(SimpleHTTPRequestHandler):
  21. """
  22. Serves files by regenerating the resource (or)
  23. everything when a request is issued.
  24. """
  25. def do_GET(self):
  26. """
  27. Identify the requested path. If the query string
  28. contains `refresh`, regenerat the entire site.
  29. Otherwise, regenerate only the requested resource
  30. and serve.
  31. """
  32. self.server.request_time = datetime.now()
  33. logger.debug("Processing request: [%s]" % self.path)
  34. result = urlparse.urlparse(self.path)
  35. query = urlparse.parse_qs(result.query)
  36. if 'refresh' in query or result.query=='refresh':
  37. self.server.regenerate()
  38. if 'refresh' in query:
  39. del query['refresh']
  40. parts = list(tuple(result))
  41. parts[4] = urllib.urlencode(query)
  42. parts = tuple(parts)
  43. new_url = urlparse.urlunparse(parts)
  44. logger.info('Redirecting... [%s]' % new_url)
  45. self.redirect(new_url)
  46. else:
  47. SimpleHTTPRequestHandler.do_GET(self)
  48. def translate_path(self, path):
  49. """
  50. Finds the absolute path of the requested file by
  51. referring to the `site` variable in the server.
  52. """
  53. path = SimpleHTTPRequestHandler.translate_path(self, path)
  54. site = self.server.site
  55. result = urlparse.urlparse(self.path)
  56. logger.debug("Trying to load file based on request: [%s]" % result.path)
  57. path = result.path.lstrip('/')
  58. res = None
  59. if path.strip() == "" or File(path).kind.strip() == "":
  60. deployed = site.config.deploy_root_path.child(path)
  61. deployed = Folder.file_or_folder(deployed)
  62. if isinstance(deployed, Folder):
  63. node = site.content.node_from_relative_path(path)
  64. res = node.get_resource('index.html')
  65. elif hasattr(site.config, 'urlcleaner') and hasattr(site.config.urlcleaner, 'strip_extensions'):
  66. for ext in site.config.urlcleaner.strip_extensions:
  67. res = site.content.resource_from_relative_deploy_path(path + '.' + ext)
  68. if res:
  69. break
  70. else:
  71. res = site.content.resource_from_relative_deploy_path(path)
  72. if not res:
  73. logger.error("Cannot load file: [%s]" % path)
  74. return site.config.deploy_root_path.child(path)
  75. else:
  76. self.server.generate_resource(res)
  77. new_path = site.config.deploy_root_path.child(
  78. res.relative_deploy_path)
  79. return new_path
  80. def do_404(self):
  81. """
  82. Sends a 'not found' response.
  83. """
  84. site = self.server.site
  85. if self.path != site.config.not_found:
  86. self.redirect(site.config.not_found)
  87. else:
  88. res = site.content.resource_from_relative_deploy_path(
  89. site.config.not_found)
  90. message = "Requested resource not found"
  91. if not res:
  92. logger.error(
  93. "Cannot find the 404 template [%s]."
  94. % site.config.not_found)
  95. else:
  96. f404 = File(self.translate_path(site.config.not_found))
  97. if f404.exists:
  98. message = f404.read_all()
  99. self.send_response(200, message)
  100. def redirect(self, path, temporary=True):
  101. """
  102. Sends a redirect header with the new location.
  103. """
  104. self.send_response(302 if temporary else 301)
  105. self.send_header('Location', path)
  106. self.end_headers()
  107. class HydeWebServer(HTTPServer):
  108. """
  109. The hyde web server that regenerates the resource, node or site when
  110. a request is issued.
  111. """
  112. def __init__(self, site, address, port):
  113. self.site = site
  114. self.site.load()
  115. self.generator = Generator(self.site)
  116. self.request_time = datetime.strptime('1-1-1999', '%m-%d-%Y')
  117. self.regeneration_time = datetime.strptime('1-1-1998', '%m-%d-%Y')
  118. self.__is_shut_down = threading.Event()
  119. self.__shutdown_request = False
  120. self.map_extensions()
  121. HTTPServer.__init__(self, (address, port),
  122. HydeRequestHandler)
  123. def map_extensions(self):
  124. """
  125. Maps extensions specified in the configuration.
  126. """
  127. try:
  128. extensions = self.site.config.server.extensions.to_dict()
  129. except AttributeError:
  130. extensions = {}
  131. for extension, type in extensions.iteritems():
  132. ext = "." + extension if not extension == 'default' else ''
  133. HydeRequestHandler.extensions_map[ext] = type
  134. ####### Code from python 2.7.1: Socket server
  135. ####### Duplicated to make sure shutdown works in Python v > 2.6
  136. #######
  137. def serve_forever(self, poll_interval=0.5):
  138. """Handle one request at a time until shutdown.
  139. Polls for shutdown every poll_interval seconds. Ignores
  140. self.timeout. If you need to do periodic tasks, do them in
  141. another thread.
  142. """
  143. self.__is_shut_down.clear()
  144. try:
  145. while not self.__shutdown_request:
  146. # XXX: Consider using another file descriptor or
  147. # connecting to the socket to wake this up instead of
  148. # polling. Polling reduces our responsiveness to a
  149. # shutdown request and wastes cpu at all other times.
  150. r, w, e = select.select([self], [], [], poll_interval)
  151. if self in r:
  152. self._handle_request_noblock()
  153. finally:
  154. self.__shutdown_request = False
  155. self.__is_shut_down.set()
  156. def shutdown(self):
  157. """Stops the serve_forever loop.
  158. Blocks until the loop has finished. This must be called while
  159. serve_forever() is running in another thread, or it will
  160. deadlock.
  161. """
  162. self.__shutdown_request = True
  163. self.__is_shut_down.wait()
  164. ############## Duplication End.
  165. def regenerate(self):
  166. """
  167. Regenerates the entire site.
  168. """
  169. try:
  170. logger.info('Regenerating the entire site')
  171. self.regeneration_time = datetime.now()
  172. self.site.load()
  173. self.generator.generate_all(incremental=False)
  174. except Exception, exception:
  175. logger.error('Error occured when regenerating the site [%s]'
  176. % exception.message)
  177. def generate_node(self, node):
  178. """
  179. Generates the given node.
  180. """
  181. deploy = self.site.config.deploy_root_path
  182. if not deploy.exists:
  183. return self.regenerate()
  184. try:
  185. logger.debug('Serving node [%s]' % node)
  186. self.generator.generate_node(node, incremental=True)
  187. except Exception, exception:
  188. logger.error(
  189. 'Error [%s] occured when generating the node [%s]'
  190. % (repr(exception), node))
  191. def generate_resource(self, resource):
  192. """
  193. Regenerates the given resource.
  194. """
  195. deploy = self.site.config.deploy_root_path
  196. if not deploy.exists:
  197. return self.regenerate()
  198. dest = deploy.child_folder(resource.node.relative_path)
  199. if not dest.exists:
  200. return self.generate_node(resource.node)
  201. try:
  202. logger.debug('Serving resource [%s]' % resource)
  203. self.generator.generate_resource(resource, incremental=True)
  204. except Exception, exception:
  205. logger.error(
  206. 'Error [%s] occured when serving the resource [%s]'
  207. % (repr(exception), resource))