Mercurial > piecrust2
changeset 374:fa3ee8a8ee2d
serve: Split the server code in a couple modules inside a `serving` package.
This makes the `serve` command's code a bit more removed from implementation
details, and paves the way for the CMS mode.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 07 May 2015 21:37:38 -0700 |
parents | 9fb7c4921d75 |
children | aade4ea57e7f |
files | piecrust/commands/builtin/serving.py piecrust/mime.types piecrust/serving.py piecrust/serving/__init__.py piecrust/serving/mime.types piecrust/serving/procloop.py piecrust/serving/server.py piecrust/serving/wrappers.py |
diffstat | 7 files changed, 1323 insertions(+), 1276 deletions(-) [+] |
line wrap: on
line diff
--- a/piecrust/commands/builtin/serving.py Thu May 07 21:36:17 2015 -0700 +++ b/piecrust/commands/builtin/serving.py Thu May 07 21:37:38 2015 -0700 @@ -1,6 +1,6 @@ import logging -from piecrust.serving import Server, _sse_abort from piecrust.commands.base import ChefCommand +from piecrust.serving.wrappers import run_werkzeug_server, run_gunicorn_server logger = logging.getLogger(__name__) @@ -37,54 +37,31 @@ default='werkzeug') def run(self, ctx): + root_dir = ctx.app.root_dir host = ctx.args.address port = int(ctx.args.port) debug = ctx.args.debug or ctx.args.use_debugger - server = Server( - ctx.app.root_dir, - debug=debug, - sub_cache_dir=ctx.app.sub_cache_dir, - use_reloader=ctx.args.use_reloader) - app = server.getWsgiApp() - if ctx.args.wsgi == 'werkzeug': - from werkzeug.serving import run_simple - try: - run_simple(host, port, app, - threaded=True, - use_debugger=debug, - use_reloader=ctx.args.use_reloader) - finally: - _sse_abort.set() + run_werkzeug_server( + root_dir, host, port, + debug_piecrust=debug, + sub_cache_dir=ctx.app.sub_cache_dir, + use_debugger=debug, + use_reloader=ctx.args.use_reloader) elif ctx.args.wsgi == 'gunicorn': - from gunicorn.app.base import BaseApplication - - class PieCrustGunicornApplication(BaseApplication): - def __init__(self, app, options): - self.app = app - self.options = options - super(PieCrustGunicornApplication, self).__init__() - - def load_config(self): - for k, v in self.options.items(): - if k in self.cfg.settings and v is not None: - self.cfg.set(k, v) - - def load(self): - return self.app - options = { 'bind': '%s:%s' % (host, port), - 'accesslog': '-', - 'worker_class': 'gaiohttp', - 'workers': 2, - 'timeout': 999999} + 'accesslog': '-', # print access log to stderr + } if debug: options['loglevel'] = 'debug' if ctx.args.use_reloader: options['reload'] = True - app_wrapper = PieCrustGunicornApplication(app, options) - app_wrapper.run() + run_gunicorn_server( + root_dir, + debug_piecrust=debug, + sub_cache_dir=ctx.app.sub_cache_dir, + gunicorn_options=options)
--- a/piecrust/mime.types Thu May 07 21:36:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,617 +0,0 @@ -application/activemessage -application/andrew-inset ez -application/applefile -application/atomicmail -application/batch-SMTP -application/beep+xml -application/cals-1840 -application/commonground -application/cu-seeme csm cu -application/cybercash -application/dca-rft -application/dec-dx -application/dsptype tsp -application/dvcs -application/edi-consent -application/edifact -application/edi-x12 -application/eshop -application/font-tdpfr -application/futuresplash spl -application/ghostview -application/hta hta -application/http -application/hyperstudio -application/iges -application/index -application/index.cmd -application/index.obj -application/index.response -application/index.vnd -application/iotp -application/ipp -application/isup -application/mac-compactpro cpt -application/marc -application/mac-binhex40 hqx -application/macwriteii -application/mathematica nb -application/mathematica-old -application/msaccess mdb -application/msword doc dot -application/news-message-id -application/news-transmission -application/octet-stream bin -application/ocsp-request -application/ocsp-response -application/oda oda -application/ogg ogg -application/parityfec -application/pics-rules prf -application/pgp-encrypted -application/pgp-keys key -application/pdf pdf -application/pgp-signature pgp -application/pkcs10 -application/pkcs7-mime -application/pkcs7-signature -application/pkix-cert -application/pkixcmp -application/pkix-crl -application/postscript ps ai eps -application/prs.alvestrand.titrax-sheet -application/prs.cww -application/prs.nprend -application/qsig -application/riscos -application/remote-printing -application/rss+xml rss -application/rtf rtf -application/sdp -application/set-payment -application/set-payment-initiation -application/set-registration -application/set-registration-initiation -application/sgml -application/sgml-open-catalog -application/sieve -application/slate -application/smil smi smil -application/timestamp-query -application/timestamp-reply -application/vemmi -application/whoispp-query -application/whoispp-response -application/wita -application/wordperfect5.1 wp5 -application/x400-bp -application/xhtml+xml xht xhtml -application/xml -application/xml-dtd -application/xml-external-parsed-entity -application/zip zip -application/vnd.3M.Post-it-Notes -application/vnd.accpac.simply.aso -application/vnd.accpac.simply.imp -application/vnd.acucobol -application/vnd.aether.imp -application/vnd.anser-web-certificate-issue-initiation -application/vnd.anser-web-funds-transfer-initiation -application/vnd.audiograph -application/vnd.bmi -application/vnd.businessobjects -application/vnd.canon-cpdl -application/vnd.canon-lips -application/vnd.cinderella cdy -application/vnd.claymore -application/vnd.commerce-battelle -application/vnd.commonspace -application/vnd.comsocaller -application/vnd.contact.cmsg -application/vnd.cosmocaller -application/vnd.ctc-posml -application/vnd.cups-postscript -application/vnd.cups-raster -application/vnd.cups-raw -application/vnd.cybank -application/vnd.dna -application/vnd.dpgraph -application/vnd.dxr -application/vnd.ecdis-update -application/vnd.ecowin.chart -application/vnd.ecowin.filerequest -application/vnd.ecowin.fileupdate -application/vnd.ecowin.series -application/vnd.ecowin.seriesrequest -application/vnd.ecowin.seriesupdate -application/vnd.enliven -application/vnd.epson.esf -application/vnd.epson.msf -application/vnd.epson.quickanime -application/vnd.epson.salt -application/vnd.epson.ssf -application/vnd.ericsson.quickcall -application/vnd.eudora.data -application/vnd.fdf -application/vnd.ffsns -application/vnd.flographit -application/vnd.framemaker -application/vnd.fsc.weblaunch -application/vnd.fujitsu.oasys -application/vnd.fujitsu.oasys2 -application/vnd.fujitsu.oasys3 -application/vnd.fujitsu.oasysgp -application/vnd.fujitsu.oasysprs -application/vnd.fujixerox.ddd -application/vnd.fujixerox.docuworks -application/vnd.fujixerox.docuworks.binder -application/vnd.fut-misnet -application/vnd.grafeq -application/vnd.groove-account -application/vnd.groove-identity-message -application/vnd.groove-injector -application/vnd.groove-tool-message -application/vnd.groove-tool-template -application/vnd.groove-vcard -application/vnd.hhe.lesson-player -application/vnd.hp-HPGL -application/vnd.hp-PCL -application/vnd.hp-PCLXL -application/vnd.hp-hpid -application/vnd.hp-hps -application/vnd.httphone -application/vnd.hzn-3d-crossword -application/vnd.ibm.MiniPay -application/vnd.ibm.afplinedata -application/vnd.ibm.modcap -application/vnd.informix-visionary -application/vnd.intercon.formnet -application/vnd.intertrust.digibox -application/vnd.intertrust.nncp -application/vnd.intu.qbo -application/vnd.intu.qfx -application/vnd.irepository.package+xml -application/vnd.is-xpr -application/vnd.japannet-directory-service -application/vnd.japannet-jpnstore-wakeup -application/vnd.japannet-payment-wakeup -application/vnd.japannet-registration -application/vnd.japannet-registration-wakeup -application/vnd.japannet-setstore-wakeup -application/vnd.japannet-verification -application/vnd.japannet-verification-wakeup -application/vnd.koan -application/vnd.lotus-1-2-3 -application/vnd.lotus-approach -application/vnd.lotus-freelance -application/vnd.lotus-notes -application/vnd.lotus-organizer -application/vnd.lotus-screencam -application/vnd.lotus-wordpro -application/vnd.mcd -application/vnd.mediastation.cdkey -application/vnd.meridian-slingshot -application/vnd.mif mif -application/vnd.minisoft-hp3000-save -application/vnd.mitsubishi.misty-guard.trustweb -application/vnd.mobius.daf -application/vnd.mobius.dis -application/vnd.mobius.msl -application/vnd.mobius.plc -application/vnd.mobius.txf -application/vnd.motorola.flexsuite -application/vnd.motorola.flexsuite.adsi -application/vnd.motorola.flexsuite.fis -application/vnd.motorola.flexsuite.gotap -application/vnd.motorola.flexsuite.kmr -application/vnd.motorola.flexsuite.ttc -application/vnd.motorola.flexsuite.wem -application/vnd.mozilla.xul+xml -application/vnd.ms-artgalry -application/vnd.ms-asf -application/vnd.ms-excel xls xlb -application/vnd.ms-lrm -application/vnd.ms-pki.seccat cat -application/vnd.ms-pki.stl stl -application/vnd.ms-powerpoint ppt pps pot -application/vnd.ms-project -application/vnd.ms-tnef -application/vnd.ms-works -application/vnd.mseq -application/vnd.msign -application/vnd.music-niff -application/vnd.musician -application/vnd.netfpx -application/vnd.noblenet-directory -application/vnd.noblenet-sealer -application/vnd.noblenet-web -application/vnd.novadigm.EDM -application/vnd.novadigm.EDX -application/vnd.novadigm.EXT -application/vnd.osa.netdeploy -application/vnd.palm -application/vnd.pg.format -application/vnd.pg.osasli -application/vnd.powerbuilder6 -application/vnd.powerbuilder6-s -application/vnd.powerbuilder7 -application/vnd.powerbuilder7-s -application/vnd.powerbuilder75 -application/vnd.powerbuilder75-s -application/vnd.previewsystems.box -application/vnd.publishare-delta-tree -application/vnd.pvi.ptid1 -application/vnd.pwg-xhtml-print+xml -application/vnd.rapid -application/vnd.s3sms -application/vnd.seemail -application/vnd.shana.informed.formdata -application/vnd.shana.informed.formtemplate -application/vnd.shana.informed.interchange -application/vnd.shana.informed.package -application/vnd.sss-cod -application/vnd.sss-dtf -application/vnd.sss-ntf -application/vnd.stardivision.calc sdc -application/vnd.stardivision.draw sda -application/vnd.stardivision.impress sdd sdp -application/vnd.stardivision.math smf -application/vnd.stardivision.writer sdw vor -application/vnd.stardivision.writer-global sgl -application/vnd.street-stream -application/vnd.sun.xml.calc sxc -application/vnd.sun.xml.calc.template stc -application/vnd.sun.xml.draw sxd -application/vnd.sun.xml.draw.template std -application/vnd.sun.xml.impress sxi -application/vnd.sun.xml.impress.template sti -application/vnd.sun.xml.math sxm -application/vnd.sun.xml.writer sxw -application/vnd.sun.xml.writer.global sxg -application/vnd.sun.xml.writer.template stw -application/vnd.svd -application/vnd.swiftview-ics -application/vnd.triscape.mxs -application/vnd.trueapp -application/vnd.truedoc -application/vnd.tve-trigger -application/vnd.ufdl -application/vnd.uplanet.alert -application/vnd.uplanet.alert-wbxml -application/vnd.uplanet.bearer-choice -application/vnd.uplanet.bearer-choice-wbxml -application/vnd.uplanet.cacheop -application/vnd.uplanet.cacheop-wbxml -application/vnd.uplanet.channel -application/vnd.uplanet.channel-wbxml -application/vnd.uplanet.list -application/vnd.uplanet.list-wbxml -application/vnd.uplanet.listcmd -application/vnd.uplanet.listcmd-wbxml -application/vnd.uplanet.signal -application/vnd.vcx -application/vnd.vectorworks -application/vnd.vidsoft.vidconference -application/vnd.visio -application/vnd.vividence.scriptfile -application/vnd.wap.sic -application/vnd.wap.slc -application/vnd.wap.wbxml wbxml -application/vnd.wap.wmlc wmlc -application/vnd.wap.wmlscriptc wmlsc -application/vnd.webturbo -application/vnd.wrq-hp3000-labelled -application/vnd.wt.stf -application/vnd.xara -application/vnd.xfdl -application/vnd.yellowriver-custom-menu -application/x-123 wk -application/x-apple-diskimage dmg -application/x-bcpio bcpio -application/x-cdf cdf -application/x-cdlink vcd -application/x-chess-pgn pgn -application/x-core -application/x-cpio cpio -application/x-csh csh -application/x-debian-package deb -application/x-director dcr dir dxr -application/x-doom wad -application/x-dms dms -application/x-dvi dvi -application/x-executable -application/x-font pfa pfb gsf pcf pcf.Z -application/x-futuresplash spl -application/x-gnumeric gnumeric -application/x-go-sgf sgf -application/x-graphing-calculator gcf -application/x-gtar gtar tgz taz -application/x-hdf hdf -application/x-httpd-php phtml pht php -application/x-httpd-php-source phps -application/x-httpd-php3 php3 -application/x-httpd-php3-preprocessed php3p -application/x-httpd-php4 php4 -application/x-ica ica -application/x-internet-signup ins isp -application/x-iphone iii -application/x-java-applet -application/x-java-archive jar -application/x-java-bean -application/x-java-jnlp-file jnlp -application/x-java-serialized-object ser -application/x-java-vm class -application/x-javascript js -application/x-kdelnk -application/x-kchart chrt -application/x-killustrator kil -application/x-kpresenter kpr kpt -application/x-koan skp skd skt skm -application/x-kspread ksp -application/x-kword kwd kwt -application/x-latex latex -application/x-lha lha -application/x-lzh lzh -application/x-lzx lzx -application/x-maker frm maker frame fm fb book fbdoc -application/x-mif mif -application/x-ms-wmz wmz -application/x-ms-wmd wmd -application/x-msdos-program com exe bat dll -application/x-msi msi -application/x-netcdf nc -application/x-ns-proxy-autoconfig pac -application/x-object o -application/x-oz-application oza -application/x-perl pl pm -application/x-pkcs7-certreqresp p7r -application/x-pkcs7-crl crl -application/x-quicktimeplayer qtl -application/x-redhat-package-manager rpm -application/x-rx -application/x-sh -application/x-shar shar -application/x-shellscript -application/x-shockwave-flash swf swfl -application/x-sh sh -application/x-stuffit sit -application/x-sv4cpio sv4cpio -application/x-sv4crc sv4crc -application/x-tar tar -application/x-tcl tcl -application/x-tex tex -application/x-tex-gf gf -application/x-tex-pk pk -application/x-texinfo texinfo texi -application/x-trash ~ % bak old sik -application/x-troff t tr roff -application/x-troff-man man -application/x-troff-me me -application/x-troff-ms ms -application/x-ustar ustar -application/x-wais-source src -application/x-wingz wz -application/x-x509-ca-cert crt -application/x-xfig fig - -audio/32kadpcm -#audio/aiff aif aifc aiff -audio/basic au snd -audio/g.722.1 -audio/l16 -audio/midi mid midi kar -audio/mp4a-latm -audio/mpa-robust -audio/mpeg mpga mpega mp2 mp3 -audio/mpegurl m3u -audio/parityfec -audio/prs.sid sid -audio/telephone-event -audio/tone -#audio/wav wav -audio/vnd.cisco.nse -audio/vnd.cns.anp1 -audio/vnd.cns.inf1 -audio/vnd.digital-winds -audio/vnd.everad.plj -audio/vnd.lucent.voice -audio/vnd.nortel.vbk -audio/vnd.nuera.ecelp4800 -audio/vnd.nuera.ecelp7470 -audio/vnd.nuera.ecelp9600 -audio/vnd.octel.sbc -audio/vnd.qcelp -audio/vnd.rhetorex.32kadpcm -audio/vnd.vmx.cvsd -audio/x-aiff aif aiff aifc -audio/x-gsm gsm -audio/x-mpegurl m3u -audio/x-ms-wma wma -audio/x-ms-wax wax -audio/x-pn-realaudio-plugin rpm -audio/x-pn-realaudio ra rm ram -audio/x-realaudio ra -audio/x-scpls pls -audio/x-sd2 sd2 -audio/x-wav wav - -chemical/x-pdb pdb -chemical/x-xyz xyz - -image/bmp bmp -image/cgm -image/g3fax -image/gif gif -image/ief ief -image/jpeg jpeg jpg jpe -image/naplps -image/pcx pcx -image/png png -image/prs.btif -image/prs.pti -image/svg+xml svg svgz -image/tiff tiff tif -image/vnd.cns.inf2 -image/vnd.dwg -image/vnd.dxf -image/vnd.fastbidsheet -image/vnd.fpx -image/vnd.fst -image/vnd.fujixerox.edmics-mmr -image/vnd.fujixerox.edmics-rlc -image/vnd.mix -image/vnd.net-fpx -image/vnd.svf -image/vnd.wap.wbmp wbmp -image/vnd.xiff -image/x-cmu-raster ras -image/x-coreldraw cdr -image/x-coreldrawpattern pat -image/x-coreldrawtemplate cdt -image/x-corelphotopaint cpt -image/x-djvu djvu djv -image/x-icon ico -image/x-jg art -image/x-jng jng -image/x-ms-bmp bmp -image/x-photoshop psd -image/x-portable-anymap pnm -image/x-portable-bitmap pbm -image/x-portable-graymap pgm -image/x-portable-pixmap ppm -image/x-rgb rgb -image/x-xbitmap xbm -image/x-xpixmap xpm -image/x-xwindowdump xwd - -inode/chardevice -inode/blockdevice -inode/directory-locked -inode/directory -inode/fifo -inode/socket - -message/delivery-status -message/disposition-notification -message/external-body -message/http -message/s-http -message/news -message/partial -message/rfc822 - -model/iges igs iges -model/mesh msh mesh silo -model/vnd.dwf -model/vnd.flatland.3dml -model/vnd.gdl -model/vnd.gs-gdl -model/vnd.gtw -model/vnd.mts -model/vnd.vtu -model/vrml wrl vrml - -multipart/alternative -multipart/appledouble -multipart/byteranges -multipart/digest -multipart/encrypted -multipart/form-data -multipart/header-set -multipart/mixed -multipart/parallel -multipart/related -multipart/report -multipart/signed -multipart/voice-message - -text/calendar -text/comma-separated-values csv -text/css css -text/directory -text/english -text/enriched -text/h323 323 -text/html htm html -text/iuls uls -text/mathml mml -text/parityfec -text/plain asc txt text diff -text/prs.lines.tag -text/rfc822-headers -text/richtext rtx -text/rtf rtf -text/scriptlet sct wsc -text/t140 -text/texmacs tm ts -text/tab-separated-values tsv -text/uri-list -text/vnd.abc -text/vnd.curl -text/vnd.DMClientScript -text/vnd.flatland.3dml -text/vnd.fly -text/vnd.fmi.flexstor -text/vnd.in3d.3dml -text/vnd.in3d.spot -text/vnd.IPTC.NewsML -text/vnd.IPTC.NITF -text/vnd.latex-z -text/vnd.motorola.reflex -text/vnd.ms-mediapackage -text/vnd.wap.si -text/vnd.wap.sl -text/vnd.wap.wml wml -text/vnd.wap.wmlscript wmls -text/xml xml xsl -text/x-c++hdr h++ hpp hxx hh -text/x-c++src c++ cpp cxx cc -text/x-chdr h -text/x-crontab -text/x-csh csh -text/x-csrc c -text/x-java java -text/x-makefile -text/xml-external-parsed-entity -text/x-moc moc -text/x-pascal p pas -text/x-pcs-gcd gcd -text/x-server-parsed-html shtml -text/x-setext etx -text/x-sh sh -text/x-tcl tcl tk -text/x-tex tex ltx sty cls -text/x-vcalendar vcs -text/x-vcard vcf - -#video/avi avi -video/dl dl -video/fli fli -video/gl gl -video/mpeg mpeg mpg mpe -video/quicktime qt mov -video/mp4v-es -video/parityfec -video/pointer -video/vnd.fvt -video/vnd.motorola.video -video/vnd.motorola.videop -video/vnd.mpegurl mxu -video/vnd.mts -video/vnd.nokia.interleaved-multimedia -video/vnd.vivo -video/x-dv dif dv -video/x-la-asf lsf lsx -video/x-mng mng -video/x-ms-asf asf asx -video/x-ms-wm wm -video/x-ms-wmv wmv -video/x-ms-wmx wmx -video/x-ms-wvx wvx -video/x-msvideo avi -video/x-sgi-movie movie - -x-conference/x-cooltalk ice - -x-world/x-vrml vrm vrml wrl -
--- a/piecrust/serving.py Thu May 07 21:36:17 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,621 +0,0 @@ -import io -import os -import re -import json -import gzip -import time -import queue -import os.path -import hashlib -import logging -import datetime -import threading -from werkzeug.exceptions import ( - NotFound, MethodNotAllowed, InternalServerError, HTTPException) -from werkzeug.wrappers import Request, Response -from werkzeug.wsgi import ClosingIterator, wrap_file -from jinja2 import FileSystemLoader, Environment -from piecrust.app import PieCrust -from piecrust.environment import StandardEnvironment -from piecrust.processing.base import ProcessorPipeline -from piecrust.rendering import QualifiedPage, PageRenderingContext, render_page -from piecrust.sources.base import PageFactory, MODE_PARSING -from piecrust.uriutil import split_sub_uri - - -logger = logging.getLogger(__name__) - - -_sse_abort = threading.Event() - - -class ServingEnvironment(StandardEnvironment): - pass - - -class ServeRecord(object): - def __init__(self): - self.entries = {} - - def addEntry(self, entry): - key = self._makeKey(entry.uri, entry.sub_num) - self.entries[key] = entry - - def getEntry(self, uri, sub_num): - key = self._makeKey(uri, sub_num) - return self.entries.get(key) - - def _makeKey(self, uri, sub_num): - return "%s:%s" % (uri, sub_num) - - -class ServeRecordPageEntry(object): - def __init__(self, uri, sub_num): - self.uri = uri - self.sub_num = sub_num - self.used_source_names = set() - - -class WsgiServerWrapper(object): - def __init__(self, server): - self.server = server - - def __call__(self, environ, start_response): - return self.server._run_request(environ, start_response) - - -class Server(object): - def __init__(self, root_dir, - debug=False, sub_cache_dir=None, - use_reloader=False, static_preview=True): - self.root_dir = root_dir - self.debug = debug - self.sub_cache_dir = sub_cache_dir - self.use_reloader = use_reloader - self.static_preview = static_preview - self._out_dir = None - self._page_record = None - self._proc_loop = None - self._mimetype_map = load_mimetype_map() - - def getWsgiApp(self): - # Bake all the assets so we know what we have, and so we can serve - # them to the client. We need a temp app for this. - app = PieCrust(root_dir=self.root_dir, debug=self.debug) - app._useSubCacheDir(self.sub_cache_dir) - self._out_dir = os.path.join(app.sub_cache_dir, 'server') - self._page_record = ServeRecord() - - if (not self.use_reloader or - os.environ.get('WERKZEUG_RUN_MAIN') == 'true'): - # We don't want to run the processing loop here if this isn't - # the actual process that does the serving. In most cases it is, - # but if we're using Werkzeug's reloader, then it won't be the - # first time we get there... it will only be the correct process - # the second time, when the reloading process is spawned, with the - # `WERKZEUG_RUN_MAIN` variable set. - pipeline = ProcessorPipeline(app, self._out_dir) - self._proc_loop = ProcessingLoop(pipeline) - self._proc_loop.start() - - # Run the WSGI app. - wsgi_wrapper = WsgiServerWrapper(self) - return wsgi_wrapper - - def _run_request(self, environ, start_response): - try: - return self._try_run_request(environ, start_response) - except Exception as ex: - if self.debug: - raise - return self._handle_error(ex, environ, start_response) - - def _try_run_request(self, environ, start_response): - request = Request(environ) - - # We don't support anything else than GET requests since we're - # previewing something that will be static later. - if self.static_preview and request.method != 'GET': - logger.error("Only GET requests are allowed, got %s" % - request.method) - raise MethodNotAllowed() - - # Handle special requests right away. - response = self._try_special_request(environ, request) - if response is not None: - return response(environ, start_response) - - # Also handle requests to a pipeline-built asset right away. - response = self._try_serve_asset(environ, request) - if response is not None: - return response(environ, start_response) - - # Create the app for this request. - app = PieCrust(root_dir=self.root_dir, debug=self.debug) - app._useSubCacheDir(self.sub_cache_dir) - app.config.set('site/root', '/') - app.config.set('server/is_serving', True) - if (app.config.get('site/enable_debug_info') and - '!debug' in request.args): - app.config.set('site/show_debug_info', True) - - # We'll serve page assets directly from where they are. - app.env.base_asset_url_format = '/_asset/%path%' - - # Let's see if it can be a page asset. - response = self._try_serve_page_asset(app, environ, request) - if response is not None: - return response(environ, start_response) - - # Nope. Let's see if it's an actual page. - try: - response = self._try_serve_page(app, environ, request) - return response(environ, start_response) - except (RouteNotFoundError, SourceNotFoundError) as ex: - raise NotFound(str(ex)) from ex - except HTTPException: - raise - except Exception as ex: - if app.debug: - logger.exception(ex) - raise - msg = str(ex) - logger.error(msg) - raise InternalServerError(msg) from ex - - def _try_special_request(self, environ, request): - static_mount = '/__piecrust_static/' - if request.path.startswith(static_mount): - rel_req_path = request.path[len(static_mount):] - mount = os.path.join( - os.path.dirname(__file__), - 'resources', 'server') - full_path = os.path.join(mount, rel_req_path) - try: - response = self._make_wrapped_file_response( - environ, request, full_path) - return response - except OSError: - pass - - debug_mount = '/__piecrust_debug/' - if request.path.startswith(debug_mount): - rel_req_path = request.path[len(debug_mount):] - if rel_req_path == 'pipeline_status': - provider = PipelineStatusServerSideEventProducer( - self._proc_loop.status_queue) - it = ClosingIterator(provider.run(), [provider.close]) - response = Response(it) - response.headers['Cache-Control'] = 'no-cache' - if 'text/event-stream' in request.accept_mimetypes: - response.mimetype = 'text/event-stream' - response.direct_passthrough = True - response.implicit_sequence_conversion = False - return response - - return None - - def _try_serve_asset(self, environ, request): - rel_req_path = request.path.lstrip('/').replace('/', os.sep) - if request.path.startswith('/_cache/'): - # Some stuff needs to be served directly from the cache directory, - # like LESS CSS map files. - full_path = os.path.join(self.root_dir, rel_req_path) - else: - full_path = os.path.join(self._out_dir, rel_req_path) - - try: - response = self._make_wrapped_file_response( - environ, request, full_path) - return response - except OSError: - pass - return None - - def _try_serve_page_asset(self, app, environ, request): - if not request.path.startswith('/_asset/'): - return None - - full_path = os.path.join(app.root_dir, request.path[len('/_asset/'):]) - if not os.path.isfile(full_path): - return None - - return self._make_wrapped_file_response(environ, request, full_path) - - def _try_serve_page(self, app, environ, request): - # Try to find what matches the requested URL. - req_path, page_num = split_sub_uri(app, request.path) - - routes = find_routes(app.routes, req_path) - if len(routes) == 0: - raise RouteNotFoundError("Can't find route for: %s" % req_path) - - rendered_page = None - first_not_found = None - for route, route_metadata in routes: - try: - logger.debug("Trying to render match from source '%s'." % - route.source_name) - rendered_page = self._try_render_page( - app, route, route_metadata, page_num, req_path) - if rendered_page is not None: - break - except NotFound as nfe: - if first_not_found is None: - first_not_found = nfe - else: - raise SourceNotFoundError( - "Can't find path for: %s (looked in: %s)" % - (req_path, [r.source_name for r, _ in routes])) - - # If we haven't found any good match, raise whatever exception we - # first got. Otherwise, raise a generic exception. - if rendered_page is None: - first_not_found = first_not_found or NotFound( - "This page couldn't be found.") - raise first_not_found - - # Start doing stuff. - page = rendered_page.page - rp_content = rendered_page.content - - # Profiling. - if app.config.get('site/show_debug_info'): - now_time = time.clock() - timing_info = ('%8.1f ms' % - ((now_time - app.env.start_time) * 1000.0)) - rp_content = rp_content.replace('__PIECRUST_TIMING_INFORMATION__', - timing_info) - - # Build the response. - response = Response() - - etag = hashlib.md5(rp_content.encode('utf8')).hexdigest() - if not app.debug and etag in request.if_none_match: - response.status_code = 304 - return response - - response.set_etag(etag) - response.content_md5 = etag - - cache_control = response.cache_control - if app.debug: - cache_control.no_cache = True - cache_control.must_revalidate = True - else: - cache_time = (page.config.get('cache_time') or - app.config.get('site/cache_time')) - if cache_time: - cache_control.public = True - cache_control.max_age = cache_time - - content_type = page.config.get('content_type') - if content_type and '/' not in content_type: - mimetype = content_type_map.get(content_type, content_type) - else: - mimetype = content_type - if mimetype: - response.mimetype = mimetype - - if ('gzip' in request.accept_encodings and - app.config.get('site/enable_gzip')): - try: - with io.BytesIO() as gzip_buffer: - with gzip.open(gzip_buffer, mode='wt', - encoding='utf8') as gzip_file: - gzip_file.write(rp_content) - rp_content = gzip_buffer.getvalue() - response.content_encoding = 'gzip' - except Exception: - logger.exception("Error compressing response, " - "falling back to uncompressed.") - response.set_data(rp_content) - - return response - - def _try_render_page(self, app, route, route_metadata, page_num, req_path): - # Match the route to an actual factory. - taxonomy_info = None - source = app.getSource(route.source_name) - if route.taxonomy_name is None: - factory = source.findPageFactory(route_metadata, MODE_PARSING) - if factory is None: - return None - else: - taxonomy = app.getTaxonomy(route.taxonomy_name) - route_terms = route_metadata.get(taxonomy.term_name) - if route_terms is None: - return None - - tax_page_ref = taxonomy.getPageRef(source.name) - factory = tax_page_ref.getFactory() - tax_terms = route.unslugifyTaxonomyTerm(route_terms) - route_metadata[taxonomy.term_name] = tax_terms - taxonomy_info = (taxonomy, tax_terms) - - # Build the page. - page = factory.buildPage() - # We force the rendering of the page because it could not have - # changed, but include pages that did change. - qp = QualifiedPage(page, route, route_metadata) - render_ctx = PageRenderingContext(qp, - page_num=page_num, - force_render=True) - if taxonomy_info is not None: - taxonomy, tax_terms = taxonomy_info - render_ctx.setTaxonomyFilter(taxonomy, tax_terms) - - # See if this page is known to use sources. If that's the case, - # just don't use cached rendered segments for that page (but still - # use them for pages that are included in it). - uri = qp.getUri() - assert uri == req_path - entry = self._page_record.getEntry(uri, page_num) - if (taxonomy_info is not None or entry is None or - entry.used_source_names): - cache_key = '%s:%s' % (uri, page_num) - app.env.rendered_segments_repository.invalidate(cache_key) - - # Render the page. - rendered_page = render_page(render_ctx) - - # Check if this page is a taxonomy page that actually doesn't match - # anything. - if taxonomy_info is not None: - paginator = rendered_page.data.get('pagination') - if (paginator and paginator.is_loaded and - len(paginator.items) == 0): - taxonomy = taxonomy_info[0] - message = ("This URL matched a route for taxonomy '%s' but " - "no pages have been found to have it. This page " - "won't be generated by a bake." % taxonomy.name) - raise NotFound(message) - - # Remember stuff for next time. - if entry is None: - entry = ServeRecordPageEntry(req_path, page_num) - self._page_record.addEntry(entry) - for p, pinfo in render_ctx.render_passes.items(): - entry.used_source_names |= pinfo.used_source_names - - # Ok all good. - return rendered_page - - def _make_wrapped_file_response(self, environ, request, path): - logger.debug("Serving %s" % path) - - # Check if we can return a 304 status code. - mtime = os.path.getmtime(path) - etag_str = '%s$$%s' % (path, mtime) - etag = hashlib.md5(etag_str.encode('utf8')).hexdigest() - if etag in request.if_none_match: - response = Response() - response.status_code = 304 - return response - - wrapper = wrap_file(environ, open(path, 'rb')) - response = Response(wrapper) - _, ext = os.path.splitext(path) - response.set_etag(etag) - response.last_modified = datetime.datetime.fromtimestamp(mtime) - response.mimetype = self._mimetype_map.get( - ext.lstrip('.'), 'text/plain') - return response - - def _handle_error(self, exception, environ, start_response): - code = 500 - if isinstance(exception, HTTPException): - code = exception.code - - path = 'error' - if isinstance(exception, NotFound): - path += '404' - - descriptions = self._get_exception_descriptions(exception) - - env = Environment(loader=ErrorMessageLoader()) - template = env.get_template(path) - context = {'details': descriptions} - response = Response(template.render(context), mimetype='text/html') - response.status_code = code - return response(environ, start_response) - - def _get_exception_descriptions(self, exception): - desc = [] - while exception is not None: - if isinstance(exception, HTTPException): - desc.append(exception.description) - else: - desc.append(str(exception)) - - inner_ex = exception.__cause__ - if inner_ex is None: - inner_ex = exception.__context__ - exception = inner_ex - return desc - - -class RouteNotFoundError(Exception): - pass - - -class SourceNotFoundError(Exception): - pass - - -content_type_map = { - 'html': 'text/html', - 'xml': 'text/xml', - 'txt': 'text/plain', - 'text': 'text/plain', - 'css': 'text/css', - 'xhtml': 'application/xhtml+xml', - 'atom': 'application/atom+xml', # or 'text/xml'? - 'rss': 'application/rss+xml', # or 'text/xml'? - 'json': 'application/json'} - - -def find_routes(routes, uri): - res = [] - for route in routes: - metadata = route.matchUri(uri) - if metadata is not None: - res.append((route, metadata)) - return res - - -class ErrorMessageLoader(FileSystemLoader): - def __init__(self): - base_dir = os.path.join(os.path.dirname(__file__), 'resources', - 'messages') - super(ErrorMessageLoader, self).__init__(base_dir) - - def get_source(self, env, template): - template += '.html' - return super(ErrorMessageLoader, self).get_source(env, template) - - -def load_mimetype_map(): - mimetype_map = {} - sep_re = re.compile(r'\s+') - path = os.path.join(os.path.dirname(__file__), 'mime.types') - with open(path, 'r') as f: - for line in f: - tokens = sep_re.split(line) - if len(tokens) > 1: - for t in tokens[1:]: - mimetype_map[t] = tokens[0] - return mimetype_map - - -class PipelineStatusServerSideEventProducer(object): - def __init__(self, status_queue): - self.status_queue = status_queue - self.interval = 2 - self.timeout = 60*10 - self._start_time = 0 - - def run(self): - logger.debug("Starting pipeline status SSE.") - self._start_time = time.time() - - outstr = 'event: ping\ndata: started\n\n' - yield bytes(outstr, 'utf8') - - count = 0 - while True: - if time.time() > self.timeout + self._start_time: - logger.debug("Closing pipeline status SSE, timeout reached.") - outstr = 'event: pipeline_timeout\ndata: bye\n\n' - yield bytes(outstr, 'utf8') - break - - if _sse_abort.is_set(): - break - - try: - logger.debug("Polling pipeline status queue...") - count += 1 - data = self.status_queue.get(True, self.interval) - except queue.Empty: - if count < 3: - continue - data = {'type': 'ping', 'message': 'ping'} - count = 0 - - event_type = data['type'] - outstr = 'event: %s\ndata: %s\n\n' % ( - event_type, json.dumps(data)) - logger.debug("Sending pipeline status SSE.") - yield bytes(outstr, 'utf8') - - def close(self): - logger.debug("Closing pipeline status SSE.") - - -class ProcessingLoop(threading.Thread): - def __init__(self, pipeline): - super(ProcessingLoop, self).__init__( - name='pipeline-reloader', daemon=True) - self.pipeline = pipeline - self.status_queue = queue.Queue() - self.interval = 1 - self._paths = set() - self._record = None - self._last_bake = 0 - - def run(self): - # Build the first list of known files and run the pipeline once. - app = self.pipeline.app - roots = [os.path.join(app.root_dir, r) - for r in self.pipeline.mounts.keys()] - for root in roots: - for dirpath, dirnames, filenames in os.walk(root): - self._paths |= set([os.path.join(dirpath, f) - for f in filenames]) - self._last_bake = time.time() - self._record = self.pipeline.run(save_record=False) - - while True: - for root in roots: - # For each mount root we try to find the first new or - # modified file. If any, we just run the pipeline on - # that mount. - found_new_or_modified = False - for dirpath, dirnames, filenames in os.walk(root): - for filename in filenames: - path = os.path.join(dirpath, filename) - if path not in self._paths: - logger.debug("Found new asset: %s" % path) - self._paths.add(path) - found_new_or_modified = True - break - if os.path.getmtime(path) > self._last_bake: - logger.debug("Found modified asset: %s" % path) - found_new_or_modified = True - break - - if found_new_or_modified: - break - - if found_new_or_modified: - self._runPipeline(root) - - time.sleep(self.interval) - - def _runPipeline(self, root): - self._last_bake = time.time() - try: - self._record = self.pipeline.run( - root, - previous_record=self._record, - save_record=False) - - # Update the status queue. - # (we need to clear it because there may not be a consumer - # on the other side, if the user isn't running with the - # debug window active) - while True: - try: - self.status_queue.get_nowait() - except queue.Empty: - break - - if self._record.success: - item = { - 'type': 'pipeline_success'} - self.status_queue.put_nowait(item) - else: - item = { - 'type': 'pipeline_error', - 'assets': []} - for entry in self._record.entries: - if entry.errors: - asset_item = { - 'path': entry.rel_input, - 'errors': list(entry.errors)} - item['assets'].append(asset_item) - self.status_queue.put_nowait(item) - except: - pass -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/serving/mime.types Thu May 07 21:37:38 2015 -0700 @@ -0,0 +1,617 @@ +application/activemessage +application/andrew-inset ez +application/applefile +application/atomicmail +application/batch-SMTP +application/beep+xml +application/cals-1840 +application/commonground +application/cu-seeme csm cu +application/cybercash +application/dca-rft +application/dec-dx +application/dsptype tsp +application/dvcs +application/edi-consent +application/edifact +application/edi-x12 +application/eshop +application/font-tdpfr +application/futuresplash spl +application/ghostview +application/hta hta +application/http +application/hyperstudio +application/iges +application/index +application/index.cmd +application/index.obj +application/index.response +application/index.vnd +application/iotp +application/ipp +application/isup +application/mac-compactpro cpt +application/marc +application/mac-binhex40 hqx +application/macwriteii +application/mathematica nb +application/mathematica-old +application/msaccess mdb +application/msword doc dot +application/news-message-id +application/news-transmission +application/octet-stream bin +application/ocsp-request +application/ocsp-response +application/oda oda +application/ogg ogg +application/parityfec +application/pics-rules prf +application/pgp-encrypted +application/pgp-keys key +application/pdf pdf +application/pgp-signature pgp +application/pkcs10 +application/pkcs7-mime +application/pkcs7-signature +application/pkix-cert +application/pkixcmp +application/pkix-crl +application/postscript ps ai eps +application/prs.alvestrand.titrax-sheet +application/prs.cww +application/prs.nprend +application/qsig +application/riscos +application/remote-printing +application/rss+xml rss +application/rtf rtf +application/sdp +application/set-payment +application/set-payment-initiation +application/set-registration +application/set-registration-initiation +application/sgml +application/sgml-open-catalog +application/sieve +application/slate +application/smil smi smil +application/timestamp-query +application/timestamp-reply +application/vemmi +application/whoispp-query +application/whoispp-response +application/wita +application/wordperfect5.1 wp5 +application/x400-bp +application/xhtml+xml xht xhtml +application/xml +application/xml-dtd +application/xml-external-parsed-entity +application/zip zip +application/vnd.3M.Post-it-Notes +application/vnd.accpac.simply.aso +application/vnd.accpac.simply.imp +application/vnd.acucobol +application/vnd.aether.imp +application/vnd.anser-web-certificate-issue-initiation +application/vnd.anser-web-funds-transfer-initiation +application/vnd.audiograph +application/vnd.bmi +application/vnd.businessobjects +application/vnd.canon-cpdl +application/vnd.canon-lips +application/vnd.cinderella cdy +application/vnd.claymore +application/vnd.commerce-battelle +application/vnd.commonspace +application/vnd.comsocaller +application/vnd.contact.cmsg +application/vnd.cosmocaller +application/vnd.ctc-posml +application/vnd.cups-postscript +application/vnd.cups-raster +application/vnd.cups-raw +application/vnd.cybank +application/vnd.dna +application/vnd.dpgraph +application/vnd.dxr +application/vnd.ecdis-update +application/vnd.ecowin.chart +application/vnd.ecowin.filerequest +application/vnd.ecowin.fileupdate +application/vnd.ecowin.series +application/vnd.ecowin.seriesrequest +application/vnd.ecowin.seriesupdate +application/vnd.enliven +application/vnd.epson.esf +application/vnd.epson.msf +application/vnd.epson.quickanime +application/vnd.epson.salt +application/vnd.epson.ssf +application/vnd.ericsson.quickcall +application/vnd.eudora.data +application/vnd.fdf +application/vnd.ffsns +application/vnd.flographit +application/vnd.framemaker +application/vnd.fsc.weblaunch +application/vnd.fujitsu.oasys +application/vnd.fujitsu.oasys2 +application/vnd.fujitsu.oasys3 +application/vnd.fujitsu.oasysgp +application/vnd.fujitsu.oasysprs +application/vnd.fujixerox.ddd +application/vnd.fujixerox.docuworks +application/vnd.fujixerox.docuworks.binder +application/vnd.fut-misnet +application/vnd.grafeq +application/vnd.groove-account +application/vnd.groove-identity-message +application/vnd.groove-injector +application/vnd.groove-tool-message +application/vnd.groove-tool-template +application/vnd.groove-vcard +application/vnd.hhe.lesson-player +application/vnd.hp-HPGL +application/vnd.hp-PCL +application/vnd.hp-PCLXL +application/vnd.hp-hpid +application/vnd.hp-hps +application/vnd.httphone +application/vnd.hzn-3d-crossword +application/vnd.ibm.MiniPay +application/vnd.ibm.afplinedata +application/vnd.ibm.modcap +application/vnd.informix-visionary +application/vnd.intercon.formnet +application/vnd.intertrust.digibox +application/vnd.intertrust.nncp +application/vnd.intu.qbo +application/vnd.intu.qfx +application/vnd.irepository.package+xml +application/vnd.is-xpr +application/vnd.japannet-directory-service +application/vnd.japannet-jpnstore-wakeup +application/vnd.japannet-payment-wakeup +application/vnd.japannet-registration +application/vnd.japannet-registration-wakeup +application/vnd.japannet-setstore-wakeup +application/vnd.japannet-verification +application/vnd.japannet-verification-wakeup +application/vnd.koan +application/vnd.lotus-1-2-3 +application/vnd.lotus-approach +application/vnd.lotus-freelance +application/vnd.lotus-notes +application/vnd.lotus-organizer +application/vnd.lotus-screencam +application/vnd.lotus-wordpro +application/vnd.mcd +application/vnd.mediastation.cdkey +application/vnd.meridian-slingshot +application/vnd.mif mif +application/vnd.minisoft-hp3000-save +application/vnd.mitsubishi.misty-guard.trustweb +application/vnd.mobius.daf +application/vnd.mobius.dis +application/vnd.mobius.msl +application/vnd.mobius.plc +application/vnd.mobius.txf +application/vnd.motorola.flexsuite +application/vnd.motorola.flexsuite.adsi +application/vnd.motorola.flexsuite.fis +application/vnd.motorola.flexsuite.gotap +application/vnd.motorola.flexsuite.kmr +application/vnd.motorola.flexsuite.ttc +application/vnd.motorola.flexsuite.wem +application/vnd.mozilla.xul+xml +application/vnd.ms-artgalry +application/vnd.ms-asf +application/vnd.ms-excel xls xlb +application/vnd.ms-lrm +application/vnd.ms-pki.seccat cat +application/vnd.ms-pki.stl stl +application/vnd.ms-powerpoint ppt pps pot +application/vnd.ms-project +application/vnd.ms-tnef +application/vnd.ms-works +application/vnd.mseq +application/vnd.msign +application/vnd.music-niff +application/vnd.musician +application/vnd.netfpx +application/vnd.noblenet-directory +application/vnd.noblenet-sealer +application/vnd.noblenet-web +application/vnd.novadigm.EDM +application/vnd.novadigm.EDX +application/vnd.novadigm.EXT +application/vnd.osa.netdeploy +application/vnd.palm +application/vnd.pg.format +application/vnd.pg.osasli +application/vnd.powerbuilder6 +application/vnd.powerbuilder6-s +application/vnd.powerbuilder7 +application/vnd.powerbuilder7-s +application/vnd.powerbuilder75 +application/vnd.powerbuilder75-s +application/vnd.previewsystems.box +application/vnd.publishare-delta-tree +application/vnd.pvi.ptid1 +application/vnd.pwg-xhtml-print+xml +application/vnd.rapid +application/vnd.s3sms +application/vnd.seemail +application/vnd.shana.informed.formdata +application/vnd.shana.informed.formtemplate +application/vnd.shana.informed.interchange +application/vnd.shana.informed.package +application/vnd.sss-cod +application/vnd.sss-dtf +application/vnd.sss-ntf +application/vnd.stardivision.calc sdc +application/vnd.stardivision.draw sda +application/vnd.stardivision.impress sdd sdp +application/vnd.stardivision.math smf +application/vnd.stardivision.writer sdw vor +application/vnd.stardivision.writer-global sgl +application/vnd.street-stream +application/vnd.sun.xml.calc sxc +application/vnd.sun.xml.calc.template stc +application/vnd.sun.xml.draw sxd +application/vnd.sun.xml.draw.template std +application/vnd.sun.xml.impress sxi +application/vnd.sun.xml.impress.template sti +application/vnd.sun.xml.math sxm +application/vnd.sun.xml.writer sxw +application/vnd.sun.xml.writer.global sxg +application/vnd.sun.xml.writer.template stw +application/vnd.svd +application/vnd.swiftview-ics +application/vnd.triscape.mxs +application/vnd.trueapp +application/vnd.truedoc +application/vnd.tve-trigger +application/vnd.ufdl +application/vnd.uplanet.alert +application/vnd.uplanet.alert-wbxml +application/vnd.uplanet.bearer-choice +application/vnd.uplanet.bearer-choice-wbxml +application/vnd.uplanet.cacheop +application/vnd.uplanet.cacheop-wbxml +application/vnd.uplanet.channel +application/vnd.uplanet.channel-wbxml +application/vnd.uplanet.list +application/vnd.uplanet.list-wbxml +application/vnd.uplanet.listcmd +application/vnd.uplanet.listcmd-wbxml +application/vnd.uplanet.signal +application/vnd.vcx +application/vnd.vectorworks +application/vnd.vidsoft.vidconference +application/vnd.visio +application/vnd.vividence.scriptfile +application/vnd.wap.sic +application/vnd.wap.slc +application/vnd.wap.wbxml wbxml +application/vnd.wap.wmlc wmlc +application/vnd.wap.wmlscriptc wmlsc +application/vnd.webturbo +application/vnd.wrq-hp3000-labelled +application/vnd.wt.stf +application/vnd.xara +application/vnd.xfdl +application/vnd.yellowriver-custom-menu +application/x-123 wk +application/x-apple-diskimage dmg +application/x-bcpio bcpio +application/x-cdf cdf +application/x-cdlink vcd +application/x-chess-pgn pgn +application/x-core +application/x-cpio cpio +application/x-csh csh +application/x-debian-package deb +application/x-director dcr dir dxr +application/x-doom wad +application/x-dms dms +application/x-dvi dvi +application/x-executable +application/x-font pfa pfb gsf pcf pcf.Z +application/x-futuresplash spl +application/x-gnumeric gnumeric +application/x-go-sgf sgf +application/x-graphing-calculator gcf +application/x-gtar gtar tgz taz +application/x-hdf hdf +application/x-httpd-php phtml pht php +application/x-httpd-php-source phps +application/x-httpd-php3 php3 +application/x-httpd-php3-preprocessed php3p +application/x-httpd-php4 php4 +application/x-ica ica +application/x-internet-signup ins isp +application/x-iphone iii +application/x-java-applet +application/x-java-archive jar +application/x-java-bean +application/x-java-jnlp-file jnlp +application/x-java-serialized-object ser +application/x-java-vm class +application/x-javascript js +application/x-kdelnk +application/x-kchart chrt +application/x-killustrator kil +application/x-kpresenter kpr kpt +application/x-koan skp skd skt skm +application/x-kspread ksp +application/x-kword kwd kwt +application/x-latex latex +application/x-lha lha +application/x-lzh lzh +application/x-lzx lzx +application/x-maker frm maker frame fm fb book fbdoc +application/x-mif mif +application/x-ms-wmz wmz +application/x-ms-wmd wmd +application/x-msdos-program com exe bat dll +application/x-msi msi +application/x-netcdf nc +application/x-ns-proxy-autoconfig pac +application/x-object o +application/x-oz-application oza +application/x-perl pl pm +application/x-pkcs7-certreqresp p7r +application/x-pkcs7-crl crl +application/x-quicktimeplayer qtl +application/x-redhat-package-manager rpm +application/x-rx +application/x-sh +application/x-shar shar +application/x-shellscript +application/x-shockwave-flash swf swfl +application/x-sh sh +application/x-stuffit sit +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-tar tar +application/x-tcl tcl +application/x-tex tex +application/x-tex-gf gf +application/x-tex-pk pk +application/x-texinfo texinfo texi +application/x-trash ~ % bak old sik +application/x-troff t tr roff +application/x-troff-man man +application/x-troff-me me +application/x-troff-ms ms +application/x-ustar ustar +application/x-wais-source src +application/x-wingz wz +application/x-x509-ca-cert crt +application/x-xfig fig + +audio/32kadpcm +#audio/aiff aif aifc aiff +audio/basic au snd +audio/g.722.1 +audio/l16 +audio/midi mid midi kar +audio/mp4a-latm +audio/mpa-robust +audio/mpeg mpga mpega mp2 mp3 +audio/mpegurl m3u +audio/parityfec +audio/prs.sid sid +audio/telephone-event +audio/tone +#audio/wav wav +audio/vnd.cisco.nse +audio/vnd.cns.anp1 +audio/vnd.cns.inf1 +audio/vnd.digital-winds +audio/vnd.everad.plj +audio/vnd.lucent.voice +audio/vnd.nortel.vbk +audio/vnd.nuera.ecelp4800 +audio/vnd.nuera.ecelp7470 +audio/vnd.nuera.ecelp9600 +audio/vnd.octel.sbc +audio/vnd.qcelp +audio/vnd.rhetorex.32kadpcm +audio/vnd.vmx.cvsd +audio/x-aiff aif aiff aifc +audio/x-gsm gsm +audio/x-mpegurl m3u +audio/x-ms-wma wma +audio/x-ms-wax wax +audio/x-pn-realaudio-plugin rpm +audio/x-pn-realaudio ra rm ram +audio/x-realaudio ra +audio/x-scpls pls +audio/x-sd2 sd2 +audio/x-wav wav + +chemical/x-pdb pdb +chemical/x-xyz xyz + +image/bmp bmp +image/cgm +image/g3fax +image/gif gif +image/ief ief +image/jpeg jpeg jpg jpe +image/naplps +image/pcx pcx +image/png png +image/prs.btif +image/prs.pti +image/svg+xml svg svgz +image/tiff tiff tif +image/vnd.cns.inf2 +image/vnd.dwg +image/vnd.dxf +image/vnd.fastbidsheet +image/vnd.fpx +image/vnd.fst +image/vnd.fujixerox.edmics-mmr +image/vnd.fujixerox.edmics-rlc +image/vnd.mix +image/vnd.net-fpx +image/vnd.svf +image/vnd.wap.wbmp wbmp +image/vnd.xiff +image/x-cmu-raster ras +image/x-coreldraw cdr +image/x-coreldrawpattern pat +image/x-coreldrawtemplate cdt +image/x-corelphotopaint cpt +image/x-djvu djvu djv +image/x-icon ico +image/x-jg art +image/x-jng jng +image/x-ms-bmp bmp +image/x-photoshop psd +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd + +inode/chardevice +inode/blockdevice +inode/directory-locked +inode/directory +inode/fifo +inode/socket + +message/delivery-status +message/disposition-notification +message/external-body +message/http +message/s-http +message/news +message/partial +message/rfc822 + +model/iges igs iges +model/mesh msh mesh silo +model/vnd.dwf +model/vnd.flatland.3dml +model/vnd.gdl +model/vnd.gs-gdl +model/vnd.gtw +model/vnd.mts +model/vnd.vtu +model/vrml wrl vrml + +multipart/alternative +multipart/appledouble +multipart/byteranges +multipart/digest +multipart/encrypted +multipart/form-data +multipart/header-set +multipart/mixed +multipart/parallel +multipart/related +multipart/report +multipart/signed +multipart/voice-message + +text/calendar +text/comma-separated-values csv +text/css css +text/directory +text/english +text/enriched +text/h323 323 +text/html htm html +text/iuls uls +text/mathml mml +text/parityfec +text/plain asc txt text diff +text/prs.lines.tag +text/rfc822-headers +text/richtext rtx +text/rtf rtf +text/scriptlet sct wsc +text/t140 +text/texmacs tm ts +text/tab-separated-values tsv +text/uri-list +text/vnd.abc +text/vnd.curl +text/vnd.DMClientScript +text/vnd.flatland.3dml +text/vnd.fly +text/vnd.fmi.flexstor +text/vnd.in3d.3dml +text/vnd.in3d.spot +text/vnd.IPTC.NewsML +text/vnd.IPTC.NITF +text/vnd.latex-z +text/vnd.motorola.reflex +text/vnd.ms-mediapackage +text/vnd.wap.si +text/vnd.wap.sl +text/vnd.wap.wml wml +text/vnd.wap.wmlscript wmls +text/xml xml xsl +text/x-c++hdr h++ hpp hxx hh +text/x-c++src c++ cpp cxx cc +text/x-chdr h +text/x-crontab +text/x-csh csh +text/x-csrc c +text/x-java java +text/x-makefile +text/xml-external-parsed-entity +text/x-moc moc +text/x-pascal p pas +text/x-pcs-gcd gcd +text/x-server-parsed-html shtml +text/x-setext etx +text/x-sh sh +text/x-tcl tcl tk +text/x-tex tex ltx sty cls +text/x-vcalendar vcs +text/x-vcard vcf + +#video/avi avi +video/dl dl +video/fli fli +video/gl gl +video/mpeg mpeg mpg mpe +video/quicktime qt mov +video/mp4v-es +video/parityfec +video/pointer +video/vnd.fvt +video/vnd.motorola.video +video/vnd.motorola.videop +video/vnd.mpegurl mxu +video/vnd.mts +video/vnd.nokia.interleaved-multimedia +video/vnd.vivo +video/x-dv dif dv +video/x-la-asf lsf lsx +video/x-mng mng +video/x-ms-asf asf asx +video/x-ms-wm wm +video/x-ms-wmv wmv +video/x-ms-wmx wmx +video/x-ms-wvx wvx +video/x-msvideo avi +video/x-sgi-movie movie + +x-conference/x-cooltalk ice + +x-world/x-vrml vrm vrml wrl +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/serving/procloop.py Thu May 07 21:37:38 2015 -0700 @@ -0,0 +1,146 @@ +import os +import os.path +import time +import json +import queue +import logging +import threading + + +logger = logging.getLogger(__name__) + + +_sse_abort = threading.Event() + + +class PipelineStatusServerSideEventProducer(object): + def __init__(self, status_queue): + self.status_queue = status_queue + self.interval = 2 + self.timeout = 60*10 + self._start_time = 0 + + def run(self): + logger.debug("Starting pipeline status SSE.") + self._start_time = time.time() + + outstr = 'event: ping\ndata: started\n\n' + yield bytes(outstr, 'utf8') + + count = 0 + while True: + if time.time() > self.timeout + self._start_time: + logger.debug("Closing pipeline status SSE, timeout reached.") + outstr = 'event: pipeline_timeout\ndata: bye\n\n' + yield bytes(outstr, 'utf8') + break + + if _sse_abort.is_set(): + break + + try: + logger.debug("Polling pipeline status queue...") + count += 1 + data = self.status_queue.get(True, self.interval) + except queue.Empty: + if count < 3: + continue + data = {'type': 'ping', 'message': 'ping'} + count = 0 + + event_type = data['type'] + outstr = 'event: %s\ndata: %s\n\n' % ( + event_type, json.dumps(data)) + logger.debug("Sending pipeline status SSE.") + yield bytes(outstr, 'utf8') + + def close(self): + logger.debug("Closing pipeline status SSE.") + + +class ProcessingLoop(threading.Thread): + def __init__(self, pipeline): + super(ProcessingLoop, self).__init__( + name='pipeline-reloader', daemon=True) + self.pipeline = pipeline + self.status_queue = queue.Queue() + self.interval = 1 + self._paths = set() + self._record = None + self._last_bake = 0 + + def run(self): + # Build the first list of known files and run the pipeline once. + app = self.pipeline.app + roots = [os.path.join(app.root_dir, r) + for r in self.pipeline.mounts.keys()] + for root in roots: + for dirpath, dirnames, filenames in os.walk(root): + self._paths |= set([os.path.join(dirpath, f) + for f in filenames]) + self._last_bake = time.time() + self._record = self.pipeline.run() + + while True: + for root in roots: + # For each mount root we try to find the first new or + # modified file. If any, we just run the pipeline on + # that mount. + found_new_or_modified = False + for dirpath, dirnames, filenames in os.walk(root): + for filename in filenames: + path = os.path.join(dirpath, filename) + if path not in self._paths: + logger.debug("Found new asset: %s" % path) + self._paths.add(path) + found_new_or_modified = True + break + if os.path.getmtime(path) > self._last_bake: + logger.debug("Found modified asset: %s" % path) + found_new_or_modified = True + break + + if found_new_or_modified: + break + + if found_new_or_modified: + self._runPipeline(root) + + time.sleep(self.interval) + + def _runPipeline(self, root): + self._last_bake = time.time() + try: + self._record = self.pipeline.run( + root, + previous_record=self._record, + save_record=False) + + # Update the status queue. + # (we need to clear it because there may not be a consumer + # on the other side, if the user isn't running with the + # debug window active) + while True: + try: + self.status_queue.get_nowait() + except queue.Empty: + break + + if self._record.success: + item = { + 'type': 'pipeline_success'} + self.status_queue.put_nowait(item) + else: + item = { + 'type': 'pipeline_error', + 'assets': []} + for entry in self._record.entries: + if entry.errors: + asset_item = { + 'path': entry.rel_input, + 'errors': list(entry.errors)} + item['assets'].append(asset_item) + self.status_queue.put_nowait(item) + except: + pass +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/serving/server.py Thu May 07 21:37:38 2015 -0700 @@ -0,0 +1,479 @@ +import io +import os +import re +import gzip +import time +import os.path +import hashlib +import logging +import datetime +from werkzeug.exceptions import ( + NotFound, MethodNotAllowed, InternalServerError, HTTPException) +from werkzeug.wrappers import Request, Response +from werkzeug.wsgi import ClosingIterator, wrap_file +from jinja2 import FileSystemLoader, Environment +from piecrust.app import PieCrust +from piecrust.rendering import QualifiedPage, PageRenderingContext, render_page +from piecrust.sources.base import MODE_PARSING +from piecrust.uriutil import split_sub_uri + + +logger = logging.getLogger(__name__) + + +class ServeRecord(object): + def __init__(self): + self.entries = {} + + def addEntry(self, entry): + key = self._makeKey(entry.uri, entry.sub_num) + self.entries[key] = entry + + def getEntry(self, uri, sub_num): + key = self._makeKey(uri, sub_num) + return self.entries.get(key) + + def _makeKey(self, uri, sub_num): + return "%s:%s" % (uri, sub_num) + + +class ServeRecordPageEntry(object): + def __init__(self, uri, sub_num): + self.uri = uri + self.sub_num = sub_num + self.used_source_names = set() + + +class WsgiServerWrapper(object): + def __init__(self, server): + self.server = server + + def __call__(self, environ, start_response): + return self.server._run_request(environ, start_response) + + +class Server(object): + def __init__(self, root_dir, + debug=False, sub_cache_dir=None, + static_preview=True, run_sse_check=None): + self.root_dir = root_dir + self.debug = debug + self.sub_cache_dir = sub_cache_dir + self.run_sse_check = run_sse_check + self.static_preview = static_preview + self._out_dir = None + self._page_record = None + self._proc_loop = None + self._mimetype_map = load_mimetype_map() + + def getWsgiApp(self): + # Bake all the assets so we know what we have, and so we can serve + # them to the client. We need a temp app for this. + app = PieCrust(root_dir=self.root_dir, debug=self.debug) + app._useSubCacheDir(self.sub_cache_dir) + self._out_dir = os.path.join(app.sub_cache_dir, 'server') + self._page_record = ServeRecord() + + if not self.run_sse_check or self.run_sse_check(): + # When using a server with code reloading, some implementations + # use process forking and we end up going here twice. We only want + # to start the pipeline loop in the inner process most of the + # time so we let the implementation tell us if this is OK. + from piecrust.processing.base import ProcessorPipeline + from piecrust.serving.procloop import ProcessingLoop + pipeline = ProcessorPipeline(app, self._out_dir) + self._proc_loop = ProcessingLoop(pipeline) + self._proc_loop.start() + + # Run the WSGI app. + wsgi_wrapper = WsgiServerWrapper(self) + return wsgi_wrapper + + def _run_request(self, environ, start_response): + try: + return self._try_run_request(environ, start_response) + except Exception as ex: + if self.debug: + raise + return self._handle_error(ex, environ, start_response) + + def _try_run_request(self, environ, start_response): + request = Request(environ) + + # We don't support anything else than GET requests since we're + # previewing something that will be static later. + if self.static_preview and request.method != 'GET': + logger.error("Only GET requests are allowed, got %s" % + request.method) + raise MethodNotAllowed() + + # Handle special requests right away. + response = self._try_special_request(environ, request) + if response is not None: + return response(environ, start_response) + + # Also handle requests to a pipeline-built asset right away. + response = self._try_serve_asset(environ, request) + if response is not None: + return response(environ, start_response) + + # Create the app for this request. + app = PieCrust(root_dir=self.root_dir, debug=self.debug) + app._useSubCacheDir(self.sub_cache_dir) + app.config.set('site/root', '/') + app.config.set('server/is_serving', True) + if (app.config.get('site/enable_debug_info') and + '!debug' in request.args): + app.config.set('site/show_debug_info', True) + + # We'll serve page assets directly from where they are. + app.env.base_asset_url_format = '/_asset/%path%' + + # Let's see if it can be a page asset. + response = self._try_serve_page_asset(app, environ, request) + if response is not None: + return response(environ, start_response) + + # Nope. Let's see if it's an actual page. + try: + response = self._try_serve_page(app, environ, request) + return response(environ, start_response) + except (RouteNotFoundError, SourceNotFoundError) as ex: + raise NotFound(str(ex)) from ex + except HTTPException: + raise + except Exception as ex: + if app.debug: + logger.exception(ex) + raise + msg = str(ex) + logger.error(msg) + raise InternalServerError(msg) from ex + + def _try_special_request(self, environ, request): + static_mount = '/__piecrust_static/' + if request.path.startswith(static_mount): + rel_req_path = request.path[len(static_mount):] + mount = os.path.join( + os.path.dirname(__file__), + 'resources', 'server') + full_path = os.path.join(mount, rel_req_path) + try: + response = self._make_wrapped_file_response( + environ, request, full_path) + return response + except OSError: + pass + + debug_mount = '/__piecrust_debug/' + if request.path.startswith(debug_mount): + rel_req_path = request.path[len(debug_mount):] + if rel_req_path == 'pipeline_status': + from piecrust.server.procloop import ( + PipelineStatusServerSideEventProducer) + provider = PipelineStatusServerSideEventProducer( + self._proc_loop.status_queue) + it = ClosingIterator(provider.run(), [provider.close]) + response = Response(it) + response.headers['Cache-Control'] = 'no-cache' + if 'text/event-stream' in request.accept_mimetypes: + response.mimetype = 'text/event-stream' + response.direct_passthrough = True + response.implicit_sequence_conversion = False + return response + + return None + + def _try_serve_asset(self, environ, request): + rel_req_path = request.path.lstrip('/').replace('/', os.sep) + if request.path.startswith('/_cache/'): + # Some stuff needs to be served directly from the cache directory, + # like LESS CSS map files. + full_path = os.path.join(self.root_dir, rel_req_path) + else: + full_path = os.path.join(self._out_dir, rel_req_path) + + try: + response = self._make_wrapped_file_response( + environ, request, full_path) + return response + except OSError: + pass + return None + + def _try_serve_page_asset(self, app, environ, request): + if not request.path.startswith('/_asset/'): + return None + + full_path = os.path.join(app.root_dir, request.path[len('/_asset/'):]) + if not os.path.isfile(full_path): + return None + + return self._make_wrapped_file_response(environ, request, full_path) + + def _try_serve_page(self, app, environ, request): + # Try to find what matches the requested URL. + req_path, page_num = split_sub_uri(app, request.path) + + routes = find_routes(app.routes, req_path) + if len(routes) == 0: + raise RouteNotFoundError("Can't find route for: %s" % req_path) + + rendered_page = None + first_not_found = None + for route, route_metadata in routes: + try: + logger.debug("Trying to render match from source '%s'." % + route.source_name) + rendered_page = self._try_render_page( + app, route, route_metadata, page_num, req_path) + if rendered_page is not None: + break + except NotFound as nfe: + if first_not_found is None: + first_not_found = nfe + else: + raise SourceNotFoundError( + "Can't find path for: %s (looked in: %s)" % + (req_path, [r.source_name for r, _ in routes])) + + # If we haven't found any good match, raise whatever exception we + # first got. Otherwise, raise a generic exception. + if rendered_page is None: + first_not_found = first_not_found or NotFound( + "This page couldn't be found.") + raise first_not_found + + # Start doing stuff. + page = rendered_page.page + rp_content = rendered_page.content + + # Profiling. + if app.config.get('site/show_debug_info'): + now_time = time.clock() + timing_info = ( + '%8.1f ms' % + ((now_time - app.env.start_time) * 1000.0)) + rp_content = rp_content.replace( + '__PIECRUST_TIMING_INFORMATION__', timing_info) + + # Build the response. + response = Response() + + etag = hashlib.md5(rp_content.encode('utf8')).hexdigest() + if not app.debug and etag in request.if_none_match: + response.status_code = 304 + return response + + response.set_etag(etag) + response.content_md5 = etag + + cache_control = response.cache_control + if app.debug: + cache_control.no_cache = True + cache_control.must_revalidate = True + else: + cache_time = (page.config.get('cache_time') or + app.config.get('site/cache_time')) + if cache_time: + cache_control.public = True + cache_control.max_age = cache_time + + content_type = page.config.get('content_type') + if content_type and '/' not in content_type: + mimetype = content_type_map.get(content_type, content_type) + else: + mimetype = content_type + if mimetype: + response.mimetype = mimetype + + if ('gzip' in request.accept_encodings and + app.config.get('site/enable_gzip')): + try: + with io.BytesIO() as gzip_buffer: + with gzip.open(gzip_buffer, mode='wt', + encoding='utf8') as gzip_file: + gzip_file.write(rp_content) + rp_content = gzip_buffer.getvalue() + response.content_encoding = 'gzip' + except Exception: + logger.exception("Error compressing response, " + "falling back to uncompressed.") + response.set_data(rp_content) + + return response + + def _try_render_page(self, app, route, route_metadata, page_num, req_path): + # Match the route to an actual factory. + taxonomy_info = None + source = app.getSource(route.source_name) + if route.taxonomy_name is None: + factory = source.findPageFactory(route_metadata, MODE_PARSING) + if factory is None: + return None + else: + taxonomy = app.getTaxonomy(route.taxonomy_name) + route_terms = route_metadata.get(taxonomy.term_name) + if route_terms is None: + return None + + tax_page_ref = taxonomy.getPageRef(source.name) + factory = tax_page_ref.getFactory() + tax_terms = route.unslugifyTaxonomyTerm(route_terms) + route_metadata[taxonomy.term_name] = tax_terms + taxonomy_info = (taxonomy, tax_terms) + + # Build the page. + page = factory.buildPage() + # We force the rendering of the page because it could not have + # changed, but include pages that did change. + qp = QualifiedPage(page, route, route_metadata) + render_ctx = PageRenderingContext(qp, + page_num=page_num, + force_render=True) + if taxonomy_info is not None: + taxonomy, tax_terms = taxonomy_info + render_ctx.setTaxonomyFilter(taxonomy, tax_terms) + + # See if this page is known to use sources. If that's the case, + # just don't use cached rendered segments for that page (but still + # use them for pages that are included in it). + uri = qp.getUri() + assert uri == req_path + entry = self._page_record.getEntry(uri, page_num) + if (taxonomy_info is not None or entry is None or + entry.used_source_names): + cache_key = '%s:%s' % (uri, page_num) + app.env.rendered_segments_repository.invalidate(cache_key) + + # Render the page. + rendered_page = render_page(render_ctx) + + # Check if this page is a taxonomy page that actually doesn't match + # anything. + if taxonomy_info is not None: + paginator = rendered_page.data.get('pagination') + if (paginator and paginator.is_loaded and + len(paginator.items) == 0): + taxonomy = taxonomy_info[0] + message = ("This URL matched a route for taxonomy '%s' but " + "no pages have been found to have it. This page " + "won't be generated by a bake." % taxonomy.name) + raise NotFound(message) + + # Remember stuff for next time. + if entry is None: + entry = ServeRecordPageEntry(req_path, page_num) + self._page_record.addEntry(entry) + for p, pinfo in render_ctx.render_passes.items(): + entry.used_source_names |= pinfo.used_source_names + + # Ok all good. + return rendered_page + + def _make_wrapped_file_response(self, environ, request, path): + logger.debug("Serving %s" % path) + + # Check if we can return a 304 status code. + mtime = os.path.getmtime(path) + etag_str = '%s$$%s' % (path, mtime) + etag = hashlib.md5(etag_str.encode('utf8')).hexdigest() + if etag in request.if_none_match: + response = Response() + response.status_code = 304 + return response + + wrapper = wrap_file(environ, open(path, 'rb')) + response = Response(wrapper) + _, ext = os.path.splitext(path) + response.set_etag(etag) + response.last_modified = datetime.datetime.fromtimestamp(mtime) + response.mimetype = self._mimetype_map.get( + ext.lstrip('.'), 'text/plain') + return response + + def _handle_error(self, exception, environ, start_response): + code = 500 + if isinstance(exception, HTTPException): + code = exception.code + + path = 'error' + if isinstance(exception, NotFound): + path += '404' + + descriptions = self._get_exception_descriptions(exception) + + env = Environment(loader=ErrorMessageLoader()) + template = env.get_template(path) + context = {'details': descriptions} + response = Response(template.render(context), mimetype='text/html') + response.status_code = code + return response(environ, start_response) + + def _get_exception_descriptions(self, exception): + desc = [] + while exception is not None: + if isinstance(exception, HTTPException): + desc.append(exception.description) + else: + desc.append(str(exception)) + + inner_ex = exception.__cause__ + if inner_ex is None: + inner_ex = exception.__context__ + exception = inner_ex + return desc + + +class RouteNotFoundError(Exception): + pass + + +class SourceNotFoundError(Exception): + pass + + +content_type_map = { + 'html': 'text/html', + 'xml': 'text/xml', + 'txt': 'text/plain', + 'text': 'text/plain', + 'css': 'text/css', + 'xhtml': 'application/xhtml+xml', + 'atom': 'application/atom+xml', # or 'text/xml'? + 'rss': 'application/rss+xml', # or 'text/xml'? + 'json': 'application/json'} + + +def find_routes(routes, uri): + res = [] + for route in routes: + metadata = route.matchUri(uri) + if metadata is not None: + res.append((route, metadata)) + return res + + +class ErrorMessageLoader(FileSystemLoader): + def __init__(self): + base_dir = os.path.join(os.path.dirname(__file__), 'resources', + 'messages') + super(ErrorMessageLoader, self).__init__(base_dir) + + def get_source(self, env, template): + template += '.html' + return super(ErrorMessageLoader, self).get_source(env, template) + + +def load_mimetype_map(): + mimetype_map = {} + sep_re = re.compile(r'\s+') + path = os.path.join(os.path.dirname(__file__), 'mime.types') + with open(path, 'r') as f: + for line in f: + tokens = sep_re.split(line) + if len(tokens) > 1: + for t in tokens[1:]: + mimetype_map[t] = tokens[0] + return mimetype_map +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/serving/wrappers.py Thu May 07 21:37:38 2015 -0700 @@ -0,0 +1,66 @@ +import os +from piecrust.serving.server import Server +from piecrust.serving.procloop import _sse_abort + + +def run_werkzeug_server(root_dir, host, port, + debug_piecrust=False, sub_cache_dir=None, + use_debugger=False, use_reloader=False): + from werkzeug.serving import run_simple + + def _run_sse_check(): + # We don't want to run the processing loop here if this isn't + # the actual process that does the serving. In most cases it is, + # but if we're using Werkzeug's reloader, then it won't be the + # first time we get there... it will only be the correct process + # the second time, when the reloading process is spawned, with the + # `WERKZEUG_RUN_MAIN` variable set. + return (not use_reloader or + os.environ.get('WERKZEUG_RUN_MAIN') == 'true') + + app = _get_piecrust_server(root_dir, + debug=debug_piecrust, + sub_cache_dir=sub_cache_dir, + run_sse_check=_run_sse_check) + try: + run_simple(host, port, app, + threaded=True, + use_debugger=use_debugger, + use_reloader=use_reloader) + finally: + _sse_abort.set() + + +def run_gunicorn_server(root_dir, + debug_piecrust=False, sub_cache_dir=None, + gunicorn_options=None): + from gunicorn.app.base import BaseApplication + + class PieCrustGunicornApplication(BaseApplication): + def __init__(self, app, options): + self.app = app + self.options = options + super(PieCrustGunicornApplication, self).__init__() + + def load_config(self): + for k, v in self.options.items(): + if k in self.cfg.settings and v is not None: + self.cfg.set(k, v) + + def load(self): + return self.app + + app = _get_piecrust_server(root_dir, + debug=debug_piecrust, + sub_cache_dir=sub_cache_dir) + + gunicorn_options = gunicorn_options or {} + app_wrapper = PieCrustGunicornApplication(app, gunicorn_options) + app_wrapper.run() + + +def _get_piecrust_server(root_dir, **kwargs): + server = Server(root_dir, **kwargs) + app = server.getWsgiApp() + return app +