yesod-static samples

This commit is contained in:
Michael Snoyman 2011-07-22 11:22:07 +03:00
parent 240a61a484
commit d7d91a486f
21 changed files with 844 additions and 0 deletions

View File

@ -0,0 +1,23 @@
{-# LANGUAGE QuasiQuotes, TypeFamilies, MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
import Yesod.Static
import Yesod.Dispatch
import Yesod.Core
import Network.Wai.Handler.Warp (run)
staticFiles "."
data Sample = Sample
getStatic _ = $(embed "tests")
mkYesod "Sample" [parseRoutes|
/ RootR GET
/static StaticR Static getStatic
|]
instance Yesod Sample where approot _ = ""
getRootR = do
redirectText RedirectPermanent "static"
return ()
main = toWaiApp Sample >>= run 3000

24
yesod-static/sample.hs Normal file
View File

@ -0,0 +1,24 @@
{-# LANGUAGE QuasiQuotes, TypeFamilies, MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
import Yesod.Static
import Yesod.Dispatch
import Yesod.Core
import Network.Wai.Handler.Warp (run)
import Network.Wai.Application.Static
staticFiles "."
data Sample = Sample
getStatic _ = Static $ defaultFileServerSettings { ssFolder = fileSystemLookup $ toFilePath "tests" }
mkYesod "Sample" [parseRoutes|
/ RootR GET
/static StaticR Static getStatic
|]
instance Yesod Sample where approot _ = ""
getRootR = do
redirectText RedirectPermanent "static"
return ()
main = toWaiApp Sample >>= run 3000

View File

@ -0,0 +1,25 @@
The following license covers this documentation, and the source code, except
where otherwise indicated.
Copyright 2010, Michael Snoyman. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

View File

@ -0,0 +1,7 @@
#!/usr/bin/env runhaskell
> module Main where
> import Distribution.Simple
> main :: IO ()
> main = defaultMain

View File

@ -0,0 +1 @@
Add test for /static/* relative redirects

View File

@ -0,0 +1,19 @@
{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import qualified Data.ByteString.Char8 as S8
import Network.Wai.Handler.Warp (run)
import Network.Wai.Middleware.Debug (debug)
import Network.Wai.Application.Static
import Web.Routes
main = run 3000 $ debug app
app req =
case decodePathInfo $ S8.unpack $ pathInfo req of
"static":"foo":rest -> staticAppPieces StaticSettings
{ ssFolder = ".."
, ssIndices = []
, ssListing = Just defaultListing
, ssGetMimeType = return . defaultMimeTypeByExt
} rest req
_ -> return $ responseLBS status404 [("Content-Type", "text/plain")] "Not found"

View File

@ -0,0 +1,11 @@
{-# LANGUAGE TemplateHaskell #-}
import Network.Wai.Application.Static
import Network.Wai.Handler.Warp (run)
import Data.FileEmbed
main :: IO ()
main = run 3000 $ staticApp defaultStaticSettings
{ ssFolder = embeddedLookup $ toEmbedded $(embedDir ".")
, ssIndices = []
, ssMaxAge = NoMaxAge
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

View File

@ -0,0 +1,424 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48.000000px"
height="48.000000px"
id="svg97"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/places"
sodipodi:docname="folder.svg"
inkscape:export-filename="/home/jimmac/Desktop/horlander-style3.png"
inkscape:export-xdpi="90.000000"
inkscape:export-ydpi="90.000000"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs3">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective68" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6719"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
inkscape:collect="always"
id="linearGradient5060">
<stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop5062" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5064" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6717"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
id="linearGradient5048">
<stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop5050" />
<stop
id="stop5056"
offset="0.5"
style="stop-color:black;stop-opacity:1;" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5052" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient6715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" />
<linearGradient
inkscape:collect="always"
id="linearGradient9806">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop9808" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop9810" />
</linearGradient>
<linearGradient
id="linearGradient9766">
<stop
style="stop-color:#6194cb;stop-opacity:1;"
offset="0"
id="stop9768" />
<stop
style="stop-color:#729fcf;stop-opacity:1;"
offset="1"
id="stop9770" />
</linearGradient>
<linearGradient
id="linearGradient3096">
<stop
id="stop3098"
offset="0"
style="stop-color:#424242;stop-opacity:1;" />
<stop
id="stop3100"
offset="1.0000000"
style="stop-color:#777777;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient319"
inkscape:collect="always">
<stop
id="stop320"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop321"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient1789">
<stop
style="stop-color:#202020;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop1790" />
<stop
style="stop-color:#b9b9b9;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop1791" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient1789"
id="radialGradient238"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.055022,-2.734504e-2,0.177703,1.190929,-3.572177,-7.125301)"
cx="20.706017"
cy="37.517986"
fx="20.706017"
fy="37.517986"
r="30.905205" />
<linearGradient
id="linearGradient3983">
<stop
style="stop-color:#ffffff;stop-opacity:0.87628865;"
offset="0.0000000"
id="stop3984" />
<stop
style="stop-color:#fffffe;stop-opacity:0.0000000;"
offset="1.0000000"
id="stop3985" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3983"
id="linearGradient491"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.516844,0.000000,0.000000,0.708978,-0.879573,-1.318166)"
x1="6.2297964"
y1="13.773066"
x2="9.8980894"
y2="66.834053" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="46.689312"
x2="12.853771"
y1="32.567184"
x1="13.035696"
gradientTransform="matrix(1.317489,0.000000,0.000000,0.816256,-0.879573,-1.318166)"
id="linearGradient322"
xlink:href="#linearGradient319"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="6.1802502"
x2="15.514889"
y1="31.367750"
x1="18.112709"
id="linearGradient3104"
xlink:href="#linearGradient3096"
inkscape:collect="always" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9766"
id="linearGradient9772"
x1="22.175976"
y1="36.987999"
x2="22.065331"
y2="32.050499"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9806"
id="radialGradient9812"
cx="24.35099"
cy="41.591846"
fx="24.35099"
fy="41.591846"
r="19.136078"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.242494,1.565588e-16,31.50606)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
fill="#729fcf"
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.10196078"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="54.359127"
inkscape:cy="-13.803699"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1026"
inkscape:window-height="818"
inkscape:window-x="169"
inkscape:window-y="30"
inkscape:showpageshadow="false"
stroke="#3465a4" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Folder Icon</dc:title>
<dc:date />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
<dc:source>http://jimmac.musichall.cz</dc:source>
<dc:subject>
<rdf:Bag>
<rdf:li>folder</rdf:li>
<rdf:li>directory</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Folder"
inkscape:groupmode="layer">
<g
style="display:inline"
transform="matrix(2.262383e-2,0,0,2.086758e-2,43.38343,36.36962)"
id="g6707">
<rect
style="opacity:0.40206185;color:black;fill:url(#linearGradient6715);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6709"
width="1339.6335"
height="478.35718"
x="-1559.2523"
y="-150.69685" />
<path
style="opacity:0.40206185;color:black;fill:url(#radialGradient6717);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M -219.61876,-150.68038 C -219.61876,-150.68038 -219.61876,327.65041 -219.61876,327.65041 C -76.744594,328.55086 125.78146,220.48075 125.78138,88.454235 C 125.78138,-43.572302 -33.655436,-150.68036 -219.61876,-150.68038 z "
id="path6711"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
id="path6713"
d="M -1559.2523,-150.68038 C -1559.2523,-150.68038 -1559.2523,327.65041 -1559.2523,327.65041 C -1702.1265,328.55086 -1904.6525,220.48075 -1904.6525,88.454235 C -1904.6525,-43.572302 -1745.2157,-150.68036 -1559.2523,-150.68038 z "
style="opacity:0.40206185;color:black;fill:url(#radialGradient6719);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
</g>
<path
d="M 4.5217805,38.687417 C 4.5435766,39.103721 4.9816854,39.520026 5.3979900,39.520026 L 36.725011,39.520026 C 37.141313,39.520026 37.535823,39.103721 37.514027,38.687417 L 36.577584,11.460682 C 36.555788,11.044379 36.117687,10.628066 35.701383,10.628066 L 22.430510,10.628066 C 21.945453,10.628066 21.196037,10.312477 21.028866,9.5214338 L 20.417475,6.6283628 C 20.262006,5.8926895 19.535261,5.5904766 19.118957,5.5904766 L 4.3400975,5.5904766 C 3.9237847,5.5904766 3.5292767,6.0067807 3.5510726,6.4230849 L 4.5217805,38.687417 z "
id="path216"
style="fill:url(#radialGradient238);fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#linearGradient3104);stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000"
sodipodi:nodetypes="ccccccssssccc" />
<path
sodipodi:nodetypes="cc"
id="path9788"
d="M 5.2265927,22.5625 L 35.492173,22.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
sodipodi:nodetypes="cc"
id="path9784"
d="M 5.0421736,18.5625 L 35.489104,18.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 4.9806965,12.5625 L 35.488057,12.5625"
id="path9778"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.3861577,32.5625 L 35.494881,32.5625"
id="path9798"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9800"
d="M 5.5091398,34.5625 L 35.496893,34.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.0421736,16.5625 L 35.489104,16.5625"
id="path9782"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9780"
d="M 5.0114345,14.5625 L 35.48858,14.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
sodipodi:nodetypes="cc"
id="path9776"
d="M 4.9220969,10.5625 L 20.202912,10.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.99999982;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 4.8737534,8.5624999 L 19.657487,8.5624999"
id="path9774"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.3246666,28.5625 L 35.493876,28.5625"
id="path9794"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9792"
d="M 5.2880638,26.5625 L 35.493184,26.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.2265927,24.5625 L 35.492173,24.5625"
id="path9790"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.1958537,20.5625 L 35.491649,20.5625"
id="path9786"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9796"
d="M 5.3246666,30.5625 L 35.493876,30.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.5091398,36.5625 L 35.496893,36.5625"
id="path9802"
sodipodi:nodetypes="cc" />
<path
style="color:#000000;fill:url(#linearGradient491);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:1.2138050;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:block;overflow:visible;opacity:0.45142857"
d="M 6.0683430,38.864023 C 6.0846856,39.176251 5.8874317,39.384402 5.5697582,39.280326 L 5.5697582,39.280326 C 5.2520766,39.176251 5.0330270,38.968099 5.0166756,38.655870 L 4.0689560,6.5913839 C 4.0526131,6.2791558 4.2341418,6.0906134 4.5463699,6.0906134 L 18.968420,6.0429196 C 19.280648,6.0429196 19.900363,6.3433923 20.101356,7.3651014 L 20.674845,10.180636 C 20.247791,9.7153790 20.255652,9.7010175 20.037287,9.0239299 L 19.631192,7.7647478 C 19.412142,7.0371009 18.932991,6.9328477 18.620763,6.9328477 L 5.7329889,6.9328477 C 5.4207613,6.9328477 5.2235075,7.1409999 5.2398583,7.4532364 L 6.1778636,38.968099 L 6.0683430,38.864023 z "
id="path219"
sodipodi:nodetypes="cccccccccscccccc" />
<g
style="stroke-miterlimit:4.0000000;stroke-width:0.99946535;stroke:none;fill-rule:nonzero;fill-opacity:0.75706214;fill:#ffffff"
id="g220"
transform="matrix(1.040764,0.000000,5.449252e-2,1.040764,-8.670199,2.670594)"
inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
inkscape:export-xdpi="74.800003"
inkscape:export-ydpi="74.800003">
<path
style="fill-opacity:0.50847459;fill:#ffffff"
d="M 42.417183,8.5151772 C 42.422267,8.4180642 42.289022,8.2681890 42.182066,8.2681716 L 29.150665,8.2660527 C 29.150665,8.2660527 30.062379,8.8540072 31.352477,8.8622963 L 42.405974,8.9333167 C 42.417060,8.7215889 42.408695,8.6772845 42.417183,8.5151772 z "
id="path221"
sodipodi:nodetypes="cscscs" />
</g>
<path
style="color:#000000;fill:url(#linearGradient9772);fill-opacity:1.0;fill-rule:nonzero;stroke:#3465a4;stroke-width:1.0000000;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1;visibility:visible;display:block"
d="M 39.783532,39.510620 C 40.927426,39.466556 41.746608,38.414321 41.830567,37.189615 C 42.622354,25.640928 43.489927,15.957666 43.489927,15.957666 C 43.562082,15.710182 43.322016,15.462699 43.009787,15.462699 L 8.6386304,15.462699 C 8.6386304,15.462699 6.7883113,37.329591 6.7883113,37.329591 C 6.6737562,38.311657 6.3223038,39.134309 5.2384755,39.513304 L 39.783532,39.510620 z "
id="path233"
sodipodi:nodetypes="cscccscc"
inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
inkscape:export-xdpi="74.800003"
inkscape:export-ydpi="74.800003" />
<path
sodipodi:nodetypes="ccsscsc"
id="path304"
d="M 9.6202444,16.463921 L 42.411343,16.528735 L 40.837297,36.530714 C 40.752975,37.602225 40.386619,37.958929 38.964641,37.958929 C 37.093139,37.958929 10.286673,37.926522 7.569899,37.926522 C 7.8034973,37.605711 7.9036547,36.937899 7.9049953,36.92191 L 9.6202444,16.463921 z "
style="opacity:0.46590909;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient322);stroke-width:0.99999970px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1.0000000" />
<path
sodipodi:nodetypes="ccccc"
id="path323"
d="M 9.6202481,16.223182 L 8.4536014,31.866453 C 8.4536014,31.866453 16.749756,27.718375 27.119949,27.718375 C 37.490142,27.718375 42.675239,16.223182 42.675239,16.223182 L 9.6202481,16.223182 z "
style="fill:#ffffff;fill-opacity:0.089285679;fill-rule:evenodd;stroke:none;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="pattern" />
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

View File

@ -0,0 +1,9 @@
import Network.Wai.Application.Static
import Network.Wai.Handler.Warp (run)
main :: IO ()
main = run 3000 $ staticApp defaultStaticSettings
{ ssFolder = fileSystemLookup "."
, ssMaxAge = MaxAgeForever
, ssIndices = []
}

View File

@ -0,0 +1,7 @@
import Network.Wai.Application.Static
main = do
checkPieces "." ["test.hs"] >>= print
checkPieces "." ["test.hs", ""] >>= print
checkPieces "." ["Network", ""] >>= print
checkPieces "." ["Network"] >>= print

View File

View File

@ -0,0 +1,166 @@
{-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction #-}
import Network.Wai.Application.Static
import Test.Hspec.Monadic
import Test.Hspec.QuickCheck
import Test.Hspec.HUnit ()
import Test.HUnit ((@?=), assert)
import Distribution.Simple.Utils (isInfixOf)
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy.Char8 as L8
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import System.PosixCompat.Files (getFileStatus, modificationTime)
import System.IO (stderr, hPutStrLn)
import Network.HTTP.Date
{-import System.Locale (defaultTimeLocale)-}
{-import Data.Time.Format (formatTime)-}
import Network.Wai
import Network.Wai.Test
import Network.Socket.Internal as Sock
import qualified Network.HTTP.Types as H
import Control.Monad.IO.Class (liftIO)
defRequest :: Request
defRequest = Request {
rawQueryString = ""
, queryString = []
, requestMethod = "GET"
, rawPathInfo = ""
, pathInfo = []
, requestHeaders = []
, serverName = "wai-test"
, httpVersion = H.http11
, serverPort = 80
, isSecure = False
, remoteHost = Sock.SockAddrInet 1 2
}
setRawPathInfo :: Request -> S8.ByteString -> Request
setRawPathInfo r rawPinfo =
let pInfo = T.split (== '/') $ TE.decodeUtf8 rawPinfo
in r { rawPathInfo = rawPinfo, pathInfo = pInfo }
-- debug :: String -> m0 ()
debug = liftIO . hPutStrLn stderr
main :: IO a
main = hspecX $ do
let must = liftIO . assert
let webApp = flip runSession $ staticApp defaultWebAppSettings {ssFolder = fileSystemLookup "tests"}
let fileServerApp = flip runSession $ staticApp defaultFileServerSettings {ssFolder = fileSystemLookup "tests"}
let etag = "1B2M2Y8AsgTpgAmY7PhCfg=="
let file = "a/b"
let statFile = setRawPathInfo defRequest file
describe "Pieces: pathFromPieces" $ do
it "converts to a file path" $
(pathFromPieces "prefix" [Piece "a" "a", Piece "bc" "bc"]) @?= "prefix/a/bc"
prop "each piece is in file path" $ \piecesS ->
let pieces = map (\p -> Piece p "") piecesS
in all (\p -> ("/" ++ p) `isInfixOf` (pathFromPieces "root" $ pieces)) piecesS
describe "webApp" $ do
it "403 for unsafe paths" $ webApp $
flip mapM_ ["..", "."] $ \path ->
assertStatus 403 =<<
request (setRawPathInfo defRequest path)
it "200 for hidden paths" $ webApp $
flip mapM_ [".hidden/folder.png", ".hidden/haskell.png"] $ \path ->
assertStatus 200 =<<
request (setRawPathInfo defRequest path)
it "404 for non-existant files" $ webApp $
assertStatus 404 =<<
request (setRawPathInfo defRequest "doesNotExist")
it "301 redirect when multiple slashes" $ webApp $ do
req <- request (setRawPathInfo defRequest "a//b/c")
assertStatus 301 req
assertHeader "Location" "../../a/b/c" req
let absoluteApp = flip runSession $ staticApp $ defaultWebAppSettings {
ssFolder = fileSystemLookup "tests", ssMkRedirect = \_ u -> S8.append "http://www.example.com" u
}
it "301 redirect when multiple slashes" $ absoluteApp $
flip mapM_ ["/a//b/c", "a//b/c"] $ \path -> do
req <- request (setRawPathInfo defRequest path)
assertStatus 301 req
assertHeader "Location" "http://www.example.com/a/b/c" req
describe "webApp when requesting a static asset" $ do
it "200 and etag when no etag query parameters" $ webApp $ do
req <- request statFile
assertStatus 200 req
assertNoHeader "Cache-Control" req
assertHeader "ETag" etag req
assertNoHeader "Last-Modified" req
it "200 when no cache headers and bad cache query string" $ webApp $ do
flip mapM_ [Just "cached", Nothing] $ \badETag -> do
req <- request statFile { queryString = [("etag", badETag)] }
assertStatus 301 req
assertHeader "Location" "../a/b?etag=1B2M2Y8AsgTpgAmY7PhCfg%3D%3D" req
assertNoHeader "Cache-Control" req
assertNoHeader "Last-Modified" req
it "Cache-Control set when etag parameter is correct" $ webApp $ do
req <- request statFile { queryString = [("etag", Just etag)] }
assertStatus 200 req
assertHeader "Cache-Control" "max-age=31536000" req
assertNoHeader "Last-Modified" req
it "200 when invalid in-none-match sent" $ webApp $
flip mapM_ ["cached", ""] $ \badETag -> do
req <- request statFile { requestHeaders = [("If-None-Match", badETag)] }
assertStatus 200 req
assertHeader "ETag" etag req
assertNoHeader "Last-Modified" req
it "304 when valid if-none-match sent" $ webApp $ do
req <- request statFile { requestHeaders = [("If-None-Match", etag)] }
assertStatus 304 req
assertNoHeader "Etag" req
assertNoHeader "Last-Modified" req
describe "fileServerApp" $ do
let fileDate = do
stat <- liftIO $ getFileStatus $ "tests/" ++ file
return $ formatHTTPDate . epochTimeToHTTPDate $ modificationTime stat
it "directory listing for index" $ fileServerApp $ do
resp <- request (setRawPathInfo defRequest "a/")
assertStatus 200 resp
let body = simpleBody resp
let contains a b = isInfixOf b (L8.unpack a)
must $ body `contains` "<img src=\"../.hidden/haskell.png\" />"
must $ body `contains` "<img src=\"../.hidden/folder.png\" alt=\"Folder\" />"
must $ body `contains` "<a href=\"b\">b</a>"
it "200 when invalid if-modified-since header" $ fileServerApp $ do
flip mapM_ ["123", ""] $ \badDate -> do
req <- request statFile {
requestHeaders = [("If-Modified-Since", badDate)]
}
assertStatus 200 req
assertNoHeader "Cache-Control" req
fdate <- fileDate
assertHeader "Last-Modified" fdate req
it "304 when if-modified-since matches" $ fileServerApp $ do
fdate <- fileDate
req <- request statFile {
requestHeaders = [("If-Modified-Since", fdate)]
}
assertStatus 304 req
assertNoHeader "Cache-Control" req

View File

@ -0,0 +1,8 @@
import System.Directory
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.ByteString.Char8 as S8
main = getDirectoryContents "." >>= mapM_ putStrLn . map fix
fix = T.unpack . TE.decodeUtf8 . S8.pack

View File

@ -0,0 +1,71 @@
name: wai-app-static
version: 0.3.0
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
maintainer: Michael Snoyman <michael@snoyman.com>
synopsis: WAI application for static serving
description: Also provides some helper functions and datatypes for use outside of WAI.
category: Web, Yesod
stability: Stable
cabal-version: >= 1.8
build-type: Simple
homepage: http://www.yesodweb.com/
Extra-source-files: folder.png, haskell.png
Flag print
Description: print debug info
Default: False
library
build-depends: base >= 4 && < 5
, wai >= 0.4 && < 0.5
, bytestring >= 0.9.1.4 && < 0.10
, http-types >= 0.6 && < 0.7
, transformers >= 0.2.2 && < 0.3
, unix-compat >= 0.2 && < 0.3
, directory >= 1.0 && < 1.2
, containers >= 0.2 && < 0.5
, blaze-html >= 0.4 && < 0.5
, time >= 1.1.4 && < 1.3
, old-locale >= 1.0.0.2 && < 1.1
, file-embed >= 0.0.3.1 && < 0.1
, text >= 0.5 && < 1.0
, blaze-builder >= 0.2.1.4 && < 0.4
, base64-bytestring >= 0.1 && < 0.2
, cryptohash >= 0.7 && < 0.8
, http-date
exposed-modules: Network.Wai.Application.Static
ghc-options: -Wall
extensions: CPP
if flag(print)
cpp-options: -DPRINT
test-suite runtests
hs-source-dirs: tests
main-is: runtests.hs
type: exitcode-stdio-1.0
build-depends: base >= 4 && < 5
, hspec >= 0.6
, HUnit
, unix-compat >= 0.2 && < 0.3
, time >= 1.1.4 && < 1.3
, old-locale >= 1.0.0.2 && < 1.1
, http-date
, Cabal
, wai-app-static >= 0.3
, wai-test
, wai
, http-types
, network
, bytestring
, text
, transformers
-- , containers
ghc-options: -Wall
source-repository head
type: git
location: git://github.com/snoyberg/wai-app-static.git

View File

@ -0,0 +1,48 @@
{-# LANGUAGE DeriveDataTypeable, RecordWildCards #-}
import Network.Wai.Application.Static
( StaticSettings (..), staticApp, defaultMimeType, defaultListing
, defaultMimeTypes, mimeTypeByExt
)
import Network.Wai.Handler.Warp (run)
import System.Environment (getArgs)
import System.Console.CmdArgs
import Text.Printf (printf)
import System.Directory (canonicalizePath)
import Control.Monad (unless)
import Network.Wai.Middleware.Autohead
import Network.Wai.Middleware.Debug
import Network.Wai.Middleware.Gzip
import qualified Data.Map as Map
import qualified Data.ByteString.Char8 as S8
import Control.Arrow (second)
data Args = Args
{ docroot :: FilePath
, index :: [FilePath]
, port :: Int
, noindex :: Bool
, quiet :: Bool
, verbose :: Bool
, mime :: [(String, String)]
}
deriving (Show, Data, Typeable)
defaultArgs = Args "." ["index.html", "index.htm"] 3000 False False False []
main :: IO ()
main = do
Args {..} <- cmdArgs defaultArgs
let mime' = map (second S8.pack) mime
let mimeMap = Map.fromList mime' `Map.union` defaultMimeTypes
docroot' <- canonicalizePath docroot
args <- getArgs
unless quiet $ printf "Serving directory %s on port %d with %s index files.\n" docroot' port (if noindex then "no" else show index)
let middle = gzip False
. (if verbose then debug else id)
. autohead
run port $ middle $ staticApp StaticSettings
{ ssFolder = docroot
, ssIndices = if noindex then [] else index
, ssListing = Just defaultListing
, ssGetMimeType = return . mimeTypeByExt mimeMap defaultMimeType
}

View File

@ -0,0 +1 @@
<h1>HELLO WORLD</h1>

View File