wai-cors 0.2.2 → 0.2.3
raw patch · 11 files changed
+628/−92 lines, 11 filesdep +directorydep +filepathdep +networkdep ~base-unicode-symbolsdep ~http-typesdep ~transformers
Dependencies added: directory, filepath, network, process, text, wai-cors, wai-websockets, warp, websockets
Dependency ranges changed: base-unicode-symbols, http-types, transformers, wai
Files
- CHANGELOG.md +13/−0
- README.md +59/−17
- constraints +6/−5
- examples/Scotty.hs +23/−0
- src/Network/Wai/Middleware/Cors.hs +40/−3
- test/PhantomJS.hs +70/−0
- test/Server.hs +63/−0
- test/index.html +253/−34
- test/phantomjs.js +39/−0
- test/server.hs +0/−19
- wai-cors.cabal +62/−14
CHANGELOG.md view
@@ -1,3 +1,16 @@+0.2.3+=====++* Added a test-suite to the package that uses PhantomJS to simulate a+ browser client.++* Pass on websocket requests unchanged to the application. Add documentation+ that reminds the application author to check the `Origin` header for+ websocket requests.++* Move development source repository from https://github.com/alephcloud/wai-cors+ to https://github.com/larskuhtz/wai-cors.+ 0.2.2 =====
README.md view
@@ -1,21 +1,53 @@-[](https://travis-ci.org/alephcloud/wai-cors)+[](https://travis-ci.org/larskuhtz/wai-cors) Cross-Origin Resource Sharing (CORS) For Wai ============================================ -This package provides a Haskell implemenation of CORS for+This package provides a Haskell implementation of CORS for [WAI](http://hackage.haskell.org/package/wai) that aims to be compliant with [http://www.w3.org/TR/cors](http://www.w3.org/TR/cors). +Note On Security+----------------++This implementation doesn't include any server side enforcement. By complying+with the CORS standard it enables the client (i.e. the web browser) to enforce+the CORS policy. For application authors it is strongly recommended to take+into account the security considerations in section 6.3 of the+[CORS standard](http://wwww.w3.org/TR/cors). In particular the application should+check that the value of the `Origin` header matches the expectations.++Websocket connections don't support CORS and are ignored by the CORS implementation+in this package. However Websocket requests usually (at least for some+browsers) include the @Origin@ header. Applications are expected to check the+value of this header and respond with an error in case that its content doesn't+match the expectations.++Installation+------------++Assuming the availability of recent versions of+[GHC](https://www.haskell.org/ghc/) and [cabal](https://www.haskell.org/cabal/)+this package is installed via++```.bash+cabal update+cabal install wai-cors+```+ Usage ----- -The file `test/server.hs` shows how to support simple cross-origin requests (as+The function 'simpleCors' enables support of simple cross-origin requests. More+advanced CORS policies can be enabled by passing a 'CorsResourcePolicy' to the+'cors' middleware.++The file `examples/Scotty.hs` shows how to support simple cross-origin requests (as defined in [http://www.w3.org/TR/cors](http://www.w3.org/TR/cors)) in a [scotty](http://hackage.haskell.org/package/scotty) application. -~~~{.haskell}+```.haskell {-# LANGUAGE UnicodeSyntax #-} {-# LANGUAGE OverloadedStrings #-} @@ -30,30 +62,40 @@ main = scotty 8080 $ do middleware simpleCors matchAny "/" $ text "Success"-~~~+``` -The result of following curl command will include the HTTP response +The result of following curl command will include the HTTP response header `Access-Control-Allow-Origin: *`. -~~~{.bash}+```.bash curl -i http://127.0.0.1:8888 -H 'Origin: 127.0.0.1' -v-~~~+``` Documentation for more general usage can be found in the module [Network.Wai.Middleware.Cors](http://hackage.haskell.org/package/wai-cors/docs/Network-Wai-Middleware-Cors.html). -TEST+Test ---- -Currently there is only basic support to test simple cross-origin-request from a browser.+In order to run the automated test suite [PhantomJS](http://phantomjs.org/) (at+least version 2.0) must be installed in the system. -Start server:+```.bash+cabal install --only-dependencies --enable-tests+cabal test --show-details=streaming+``` -~~~{.bash}+If [PhantomJS](http://phantomjs.org/) is not available the tests can be+exectued manually in a modern web-browser as follows.++Start the server application:++```.bash cd test-runHaskell server.hs-~~~+ghc -main-is Server Server.hs+./Server+``` -Open the file `test/index.html` in a modern web-browser in order to run some-simple tests.+Open the file `test/index.html` in a modern web-browser. On page load a Javascript+script is exectued that runs the test suite and prints the result on the page.+
constraints view
@@ -1,11 +1,12 @@ constraints: array ==0.5.1.0,- attoparsec ==0.12.1.5,+ attoparsec ==0.13.0.0, base ==4.8.0.0, base-unicode-symbols ==0.2.2.4,+ binary ==0.7.3.0, blaze-builder ==0.4.0.1, bytestring ==0.10.6.0, case-insensitive ==1.2.0.4,- charset ==0.3.7,+ charset ==0.3.7.1, containers ==0.5.6.2, deepseq ==1.4.1.1, ghc-prim ==0.4.0.0,@@ -14,13 +15,13 @@ integer-gmp ==1.0.0.0, mtl ==2.2.1, nats ==1,- network ==2.6.0.2,+ network ==2.6.1.0, parsec ==3.1.9,- parsers ==0.12.1.1,+ parsers ==0.12.2.1, rts ==1.0, scientific ==0.3.3.8, semigroups ==0.16.2.2,- text ==1.2.0.4,+ text ==1.2.1.0, time ==1.5.0.1, transformers ==0.4.2.0, unix ==2.7.1.0,
+ examples/Scotty.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE UnicodeSyntax #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module: Main+-- Description: Exampe for using wai-cors with scotty+-- Copyright: © 2015 Lars Kuhtz <lakuhtz@gmail.com>+-- License: MIT+-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>+-- Stability: experimental+--+module Main+( main+) where++import Network.Wai.Middleware.Cors+import Web.Scotty++main ∷ IO ()+main = scotty 8080 $ do+ middleware simpleCors+ matchAny "/" $ text "Success"+
src/Network/Wai/Middleware/Cors.hs view
@@ -1,4 +1,5 @@ -- ------------------------------------------------------ --+-- Copyright © 2015 Lars Kuhtz <lakuhtz@gmail.com> -- Copyright © 2014 AlephCloud Systems, Inc. -- ------------------------------------------------------ -- @@ -14,8 +15,29 @@ -- | An implemenation of Cross-Origin resource sharing (CORS) for WAI that -- aims to be compliant with <http://www.w3.org/TR/cors>. ----- The function 'simpleCors' enables support of simple cross-origin requests.+-- The function 'simpleCors' enables support of simple cross-origin requests. More+-- advanced CORS policies can be enabled by passing a 'CorsResourcePolicy' to the+-- 'cors' middleware. --+-- = Note On Security+--+-- This implementation doens't include any server side enforcement. By+-- complying with the CORS standard it enables the client (i.e. the web+-- browser) to enforce the CORS policy. For application authors it is strongly+-- recommended to take into account the security considerations in section 6.3+-- of <http://wwww.w3.org/TR/cors>. In particular the application should check+-- that the value of the @Origin@ header matches it's expectations.+--+-- = Websockets+--+-- Websocket connections don't support CORS and are ignored by this CORS+-- implementation. However Websocket requests usually (at least for some+-- browsers) include the @Origin@ header. Applications are expected to check+-- the value of this header and respond with an error in case that its content+-- doesn't match the expectations.+--+-- = Example+-- -- The following is an example how to enable support for simple cross-origin requests -- for a <http://hackage.haskell.org/package/scotty scotty> application. --@@ -58,6 +80,10 @@ #define MIN_VESION_base(x,y,z) 1 #endif +#ifndef MIN_VESION_wai+#define MIN_VESION_wai(x,y,z) 1+#endif+ #if ! MIN_VERSION_base(4,8,0) import Control.Applicative #endif@@ -147,7 +173,7 @@ -- If this is 'False' and the request does not include an @Origin@ header -- the request is passed on unchanged to the application. --- -- /since version 0.2/+ -- @since 0.2 , corsRequireOrigin ∷ !Bool -- | In the case that@@ -165,7 +191,7 @@ -- * an response with HTTP status 400 (bad request) and short -- error message is returned if this field is 'False'. --- -- /since version 0.2/+ -- @since 0.2 -- , corsIgnoreFailures ∷ !Bool }@@ -273,6 +299,9 @@ #else cors policyPattern app r #endif+ -- We don't handle websockets, even if they include an @Origin@ header+ | isWebSocketsReq r = runApp+ | Just policy ← policyPattern r = case hdrOrigin of -- No origin header: requect request@@ -519,4 +548,12 @@ ∷ B8.ByteString -- ^ body → WAI.Response corsFailure msg = WAI.responseLBS HTTP.status400 [("Content-Type", "text/html; charset-utf-8")] (LB8.fromStrict msg)++-- Copied from the [wai-websocket package](https://github.com/yesodweb/wai/blob/master/wai-websockets/Network/Wai/Handler/WebSockets.hs#L21)+--+isWebSocketsReq+ ∷ WAI.Request+ → Bool+isWebSocketsReq req =+ fmap CI.mk (lookup "upgrade" $ WAI.requestHeaders req) == Just "websocket"
+ test/PhantomJS.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UnicodeSyntax #-}++-- |+-- Module: Main+-- Description: Test suite that uses PhantomJS to simulate a browser+-- Copyright: © 2015 Lars Kuhtz <lakuhtz@gmail.com>+-- License: MIT+-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>+-- Stability: experimental+--+module Main+( main+) where++import Control.Concurrent++import Data.Monoid.Unicode+import Data.String++import System.Directory+import System.FilePath+import System.Exit+import System.IO+import System.Process++-- internal modules++import qualified Server as Server++phantomJsBinaryPath ∷ FilePath+phantomJsBinaryPath = "phantomjs"++phantomJsArgs ∷ IsString a ⇒ [a]+phantomJsArgs = ["--ignore-ssl-errors=true"]+--phantomJsArgs = ["--ignore-ssl-errors=true", "--debug=true"]++phantomJsScriptPath ∷ FilePath+phantomJsScriptPath = "test/phantomjs.js"++indexFilePath ∷ FilePath+indexFilePath = "test/index.html"++runPhantomJs ∷ IO ()+runPhantomJs = do++ -- check that phantomJS binary is available+ -- FIXME check the version+ findExecutable phantomJsBinaryPath >>= \case+ Nothing → do+ hPutStrLn stderr $ "Missing PhantomJS exectuable: in order to run this test-suite PhantomJS must be availabe on the system"+ exitFailure+ Just _ → return ()++ pwd ← getCurrentDirectory+ -- FIXME I consider it an API bug of the directory package that in order+ -- to get the exit code we have also capture stdout and stderr.+ (code, out, err) ← readProcessWithExitCode phantomJsBinaryPath (args pwd) ""+ hPutStrLn stdout out+ hPutStrLn stderr err+ exitWith code+ where+ args pwd = phantomJsArgs ⊕ [phantomJsScriptPath] ⊕ [pwd </> indexFilePath]++main ∷ IO ()+main = do+ _ ← forkIO $ Server.main+ runPhantomJs
+ test/Server.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UnicodeSyntax #-}++-- |+-- Module: Server+-- Description: Test HTTP server for wai-cors+-- Copyright: © 2015 Lars Kuhtz <lakuhtz@gmail.com>.+-- License: MIT+-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>+-- Stability: experimental+--+module Server+( main+) where++#ifndef MIN_VERSION_wai+#define MIN_VERSION_wai(a,b,c) 1+#endif++import Control.Concurrent+import Control.Exception+import Control.Monad++import Network.Socket (withSocketsDo)+import Network.Wai.Middleware.Cors+import qualified Network.HTTP.Types as HTTP+import qualified Network.Wai as WAI+import qualified Network.Wai.Handler.Warp as WARP+import qualified Network.Wai.Handler.WebSockets as WS+import qualified Network.WebSockets as WS++import qualified Data.Text as T++main ∷ IO ()+main = withSocketsDo $+ WARP.run 8080 $ simpleCors waiapp++waiapp ∷ WAI.Application+waiapp = WS.websocketsOr WS.defaultConnectionOptions wsserver $ \_ → app+ where+#if MIN_VERSION_wai(2,0,0)+ app respond = respond $ WAI.responseLBS HTTP.status200 [] "Success"+#else+ app = WAI.responseLBS HTTP.status200 [] "Success"+#endif++-- -------------------------------------------------------------------------- --+-- Websockets Server++wsserver ∷ WS.ServerApp+wsserver pc = do+ c ← WS.acceptRequest pc+ forever (go c) `catch` \case+ WS.CloseRequest _code _msg → WS.sendClose c ("closed" ∷ T.Text)+ e → throwIO e+ where+ go c = do+ msg ← WS.receiveDataMessage c+ forkIO $ WS.sendDataMessage c msg+
test/index.html view
@@ -1,64 +1,283 @@ <!DOCTYPE html>+<!-- Copyright (c) 2015 Lars Kuhtz <lakuhtz@gmail.coml> --> <html> <head> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> <script type="text/javascript"> var url = 'http://localhost:8080'- var simpleTest = function (label, result, method, creds, contentType, headers) {+ var wsurl = 'ws://localhost:8080' - var xhr = new XMLHttpRequest();- xhr.open(method, url, true);+/* ************************************************************************** */+/* Utils */ - var succ = function() {- $('#results').append('<p style="color:green">' + label + ': success</p>')+ /* asynchronous array map function+ *+ * The function f is called on each element in the value and+ * is expected to return a promise object.+ *+ * asyncMap itself returns a promise object that is completed+ * when the promises for all elements completed. When all element+ * promises resolve the array promises is resolved. If at least+ * one element promise is reject the array promises is rejected.+ *+ * The done and fail callbacks are called with an object of the form+ *+ * { 'successes':successes, 'failures': failures, 'results': results }+ *+ * The results that array has one value for each element of the original+ * array. The values in the result array are objects of the form+ *+ * { success: trueOrFalse, result: elementPromiseResult }+ *+ * The progress callback is given an object of the form+ *+ * { successes: sumS, failures: numF, missing: numberOfMissingElementPromises }+ */+ var asyncMap = function (a,f) {+ var def = $.Deferred();+ var successes = 0;+ var failures = 0;+ var results = new Array(a.length);+ var c = a.length;++ a.map(function (val, idx, arr) {+ t = f(val);+ t.done(function(arg) {+ successes++;+ results[idx] = { 'success':true, 'result':arg };+ });+ t.fail(function(arg) {+ failures++;+ results[idx] = { 'success':false, 'result':arg };+ });+ t.always(function() {+ c--;+ if (c > 0) {+ def.notify({ 'successes':successes, 'failures': failures, 'missing': c });+ } else {+ var result = { 'successes':successes, 'failures': failures, 'results': results };+ if (failures === 0) {+ def.resolve(result);+ } else {+ def.reject(result);+ };+ };+ });+ });+ return def.promise();+ };++ // monoid concatenation for asyncMap results+ var concatResults = function (r1, r2) {+ return {+ 'successes': r1.successes + r2.successes,+ 'failures': r1.failures + r2.failures,+ 'results': r1.results.concat(r2.results) };+ } - var fail = function() {- $('#results').append('<p style="color:red">' + label + ': failure</p>')+ // monoid identity for asyncMap results+ var emptyResult = { 'successes': 0, 'failures': 0, 'results':[] };++ // monad join for asyncMap results+ var flattenResults = function (r) {+ return r.results.reduce(function (p,c,i,a) { return concatResults(p,c.result); }, emptyResult);+ };++/* ************************************************************************** */+/* PhantomJS Integration */++ // Integration with PhantomJS+ //+ // call with: phantomjs --ignore-ssl-errors=true phantomjs.js $PWD/index.html+ //+ if (typeof window.callPhantom === 'function') {+ window.setTimeout(function () { window.callPhantom("timeout"); },3000);+ };++ var phantomJsCb = function (result) { + if (typeof window.callPhantom === 'function') {+ window.callPhantom(result); };+ }; - xhr.onerror = result ? fail : succ;- xhr.onload = result ? succ : fail;+/* ************************************************************************** */+/* Run Tests */ - xhr.setRequestHeader('content-type', contentType);+ var runTest = function (params) { - for (var i = 0; i < headers.length; ++i) {- var e = headers[i];- xhr.setRequestHeader(e[1], e[2]);+ var test = $.Deferred();+ var req = $.ajax(+ { 'method': params.method+ , 'url': url+ , 'contentType': params.contentType+ , 'crossDomain': true+ , 'headers': params.headers+ });++ var succ = function () { test.resolve(params); }+ var fail = function () { test.reject(params); }+ req.done(params.expectedResult ? succ : fail);+ req.fail(params.expectedResult ? fail : succ);++ return test.promise();+ }++ var pageTest = function (params) {+ test = runTest(params);++ test.done(function() {+ $('#results').append('<p style="color:green">' + params.label + ': success</p>');+ });++ test.fail(function() {+ $('#results').append('<p style="color:red">' + params.label + ': failure</p>');+ });+ return test;+ }++ var runAllPageTests = function (tests) {+ return asyncMap(tests, pageTest);+ }++/* ************************************************************************** */+/* WebSockets */++ /* a super simple ws service client+ *+ * TODO add proper callbacks (e.g. close, error);+ * TODO in case we use this for anything more complex we should add proper+ * streaming by supporting chunking.+ *+ */+ var WsService = function (url, timeout) {+ var timeout = timeout ? timeout : 1000;+ var curMsgId = 0;+ var ready = $.Deferred();+ var websocket = new WebSocket(url);+ var newMsgId = function () { return ++curMsgId; };++ // maps message ids data to promise objects+ var msgMap = {}++ var ws = function (msg) {+ var p = $.Deferred();+ var req = { id: newMsgId(), msg: msg};+ ready.then(function () { websocket.send(JSON.stringify(req)); });+ msgMap[req.id] = p;+ setTimeout(function () { p.reject("timeout: no response from websocket after " + timeout + " milliseconds"); }, timeout);+ return p.promise();+ };++ websocket.onmessage = function (evt) {+ var resp = JSON.parse(evt.data);+ var prom = msgMap[resp.id];+ if (prom) {+ prom.resolve(resp.msg);+ } else {+ console.log("unexpected message from websocket: " + JSON.stringify(resp));+ };+ };++ var wserror = function (evt) {+ for (i in msgMap) {+ if (msgMap.hasOwnProperty(i)) {+ msgMap[i].fail();+ };+ };+ msgMap = {};+ };++ websocket.onopen = function(evt) {+ ready.resolve();+ };++ websocket.onerror = function(evt) {+ console.log("websocket error: " + evt.data);+ wserror(evt);+ };++ websocket.onclose = function(evt) {+ console.log("websocket closed unexpectedly: " + evt.data);+ wserror(evt);+ };++ return ws;+ };++ var websocketTest = function () {++ var ws = WsService(wsurl);++ var wstest = function (msg) {+ var p = ws(msg);++ p.done(function() {+ $('#wsresults').append('<p style="color:green">' + msg + ': success</p>');+ });++ p.fail(function() {+ $('#wsresults').append('<p style="color:red">' + msg + ': failure</p>');+ });+ return p; }- xhr.send();++ return asyncMap(["msg 0", "msg 1", "msg 2"], wstest);+ };++/* ************************************************************************** */+/* Test Vectors */++ var mkTest = function (label, expectedResult, method, credentials, contentType, headers) {+ return {+ label: label,+ expectedResult: expectedResult,+ method: method,+ credentials: credentials,+ contentType: contentType,+ headers: headers+ }; } - $(document).ready(function() {- simpleTest("simple GET 1", true, "GET", false, 'text/plain', []);- simpleTest("simple GET 2", true, "GET", false, 'application/x-www-form-urlencoded', []);- simpleTest("simple GET 3", true, "GET", false, 'multipart/form-data', []);- simpleTest("simple GET 4", false, "GET", false, 'application/json', []);- simpleTest("simple GET 5", false, "GET", false, 'text/plain', [['abc','abc']]);+ var tests = [+ mkTest("simple GET 1", true, "GET", false, 'text/plain', []),+ mkTest("simple GET 2", true, "GET", false, 'application/x-www-form-urlencoded', []),+ mkTest("simple GET 3", true, "GET", false, 'multipart/form-data', []),+ mkTest("simple GET 4", false, "GET", false, 'application/json', []),+ mkTest("simple GET 5", false, "GET", false, 'text/plain', {'abc': 'abc'}), - simpleTest("simple HEAD 1", true, "HEAD", false, 'text/plain', []);- simpleTest("simple HEAD 2", true, "HEAD", false, 'application/x-www-form-urlencoded', []);- simpleTest("simple HEAD 3", true, "HEAD", false, 'multipart/form-data', []);- simpleTest("simple HEAD 4", false, "HEAD", false, 'application/json', []);- simpleTest("simple HEAD 5", false, "HEAD", false, 'text/plain', [['abc','abc']]);+ mkTest("simple HEAD 1", true, "HEAD", false, 'text/plain', []),+ mkTest("simple HEAD 2", true, "HEAD", false, 'application/x-www-form-urlencoded', []),+ mkTest("simple HEAD 3", true, "HEAD", false, 'multipart/form-data', []),+ mkTest("simple HEAD 4", false, "HEAD", false, 'application/json', []),+ mkTest("simple HEAD 5", false, "HEAD", false, 'text/plain', {'abc': 'abc'}), - simpleTest("simple POST 1", true, "POST", false, 'text/plain', []);- simpleTest("simple POST 2", true, "POST", false, 'application/x-www-form-urlencoded', []);- simpleTest("simple POST 3", true, "POST", false, 'multipart/form-data', []);- simpleTest("simple POST 4", false, "POST", false, 'application/json', []);- simpleTest("simple POST 5", false, "POST", false, 'test/plain', [['abc','abc']]);+ mkTest("simple POST 1", true, "POST", false, 'text/plain', []),+ mkTest("simple POST 2", true, "POST", false, 'application/x-www-form-urlencoded', []),+ mkTest("simple POST 3", true, "POST", false, 'multipart/form-data', []),+ mkTest("simple POST 4", false, "POST", false, 'application/json', []),+ mkTest("simple POST 5", false, "POST", false, 'test/plain', {'abc':'abc'}), - simpleTest("simple PUT 1", false, "PUT", false, 'text/plain', []);- simpleTest("simple DELETE 1", false, "DELETE", false, 'text/plain', []);- simpleTest("simple PATCH 1", false, "PATCH", false, 'text/plain', []);+ mkTest("simple PUT 1", false, "PUT", false, 'text/plain', []),+ mkTest("simple DELETE 1", false, "DELETE", false, 'text/plain', []),+ mkTest("simple PATCH 1", false, "PATCH", false, 'text/plain', []),+ ];++ $(document).ready(function() {+ var p = runAllPageTests(tests);+ var q = websocketTest();+ var r = asyncMap([p,q], function (x) { return x; });+ r.always(function (x) { phantomJsCb(flattenResults(x)); }); }); </script> </head> <body> <h1>Results</h1>- <div id="results"/>+ <div id="results"></div>+ <h1>Websocket Test Results</h1>+ <div id="wsresults"></div> </body> </html>
+ test/phantomjs.js view
@@ -0,0 +1,39 @@+/*+ * Copyright (c) 2015 Lars Kuhtz <lakuhtz@gmail.com>+ */++var page = require('webpage').create();+var system = require('system');+var index = null;++if (system.args.length === 1) {+ index = 'https://rawgit.com/alephcloud/wai-cors/master/test/index.html'+} else if (system.args.length === 2) {+ index = 'file://' + system.args[1];+} else {+ console.log('Usage:');+ console.log(' phantomjs phanomjs.js <absolute-index-file-path>');+ console.log(' phantomjs phanomjs.js');+ phantom.exit(3);+}++page.open(index, function(status) {+ console.log("Status: " + status);+ if(status === "success") {+ console.log("test page loaded successfully");+ } else {+ console.log("failed to load test page");+ phantom.exit(2);+ }+});++page.onCallback = function(data) {+ if (data.failures > 0) {+ console.log("Some tests failed");+ console.log( page.plainText );+ phantom.exit(1);+ } else {+ console.log( page.plainText );+ phantom.exit(0);+ }+}
− test/server.hs
@@ -1,19 +0,0 @@--- ------------------------------------------------------ ----- Copyright © 2014 AlephCloud Systems, Inc.--- ------------------------------------------------------ ----{-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE OverloadedStrings #-}--module Main-( main-) where--import Network.Wai.Middleware.Cors-import Web.Scotty--main ∷ IO ()-main = scotty 8080 $ do- middleware simpleCors- matchAny "/" $ text "Success"-
wai-cors.cabal view
@@ -1,9 +1,10 @@ -- ------------------------------------------------------ --+-- Copyright © 2015 Lars Kuhtz <lakuhtz@gmail.com> -- Copyright © 2014 AlephCloud Systems, Inc. -- ------------------------------------------------------ -- Name: wai-cors-Version: 0.2.2+Version: 0.2.3 Synopsis: CORS for WAI Description:@@ -12,13 +13,13 @@ <http://hackage.haskell.org/package/wai Wai> that aims to be compliant with <http://www.w3.org/TR/cors>. -Homepage: https://github.com/alephcloud/wai-cors-Bug-reports: https://github.com/alephcloud/wai-cors/issues+Homepage: https://github.com/larskuhtz/wai-cors+Bug-reports: https://github.com/larskuhtz/wai-cors/issues License: MIT License-file: LICENSE-Author: Lars Kuhtz <lars@alephcloud.com>-Maintainer: Lars Kuhtz <lars@alephcloud.com>-Copyright: Copyright (c) 2014 AlephCloud Systems, Inc.+Author: Lars Kuhtz <lakuhtz@gmail.com>+Maintainer: Lars Kuhtz <lakuhtz@gmail.com>+Copyright: (c) 2015 Lars Kuhtz <lakuhtz@gmail.com>, (c) 2014 AlephCloud Systems, Inc. Category: Web Build-type: Simple Cabal-version: >= 1.14.0@@ -28,16 +29,18 @@ CHANGELOG.md constraints test/index.html- test/server.hs+ test/phantomjs.js+ examples/Scotty.hs source-repository head type: git- location: https://github.com/alephcloud/wai-cors+ location: https://github.com/larskuhtz/wai-cors+ branch: master source-repository this type: git- location: https://github.com/alephcloud/wai-cors- tag: 0.2.2+ location: https://github.com/larskuhtz/wai-cors+ tag: 0.2.3 flag transformers-3 description: use transformers<0.3 (this is set automatically)@@ -49,6 +52,11 @@ default: False manual: False +flag wai-2+ description: use version wai<3 (this is set automatically)+ default: False+ manual: False+ Library default-language: Haskell2010 hs-source-dirs: src@@ -69,10 +77,14 @@ if flag (wai-1) build-depends: wai < 2.0,- resourcet >= 0.4- else+ resourcet >= 0.4,+ transformers < 0.4+ if flag (wai-2) build-depends:- wai >= 2.0+ wai < 3.0 && >= 2.0+ if ! flag (wai-1) && ! flag (wai-2)+ build-depends:+ wai >= 3.0 if flag(transformers-3) build-depends:@@ -82,5 +94,41 @@ else build-depends: mtl >= 2.2,- transformers >= 0.4+ transformers >= 0.4,+ wai >= 2++ ghc-options: -Wall++test-suite phantomjs+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ main-is: PhantomJS.hs+ hs-source-dirs: test++ other-modules:+ Server++ build-depends:+ base == 4.*,+ base-unicode-symbols >= 0.2,+ directory >= 1.2,+ filepath >= 1.4,+ http-types >= 0.8,+ network >= 2.6,+ process >= 1.2,+ text >= 1.2,+ wai-cors++ -- we only build the tests suite with the most recent wai version+ -- cabal fails to resolves the old dependencies otherwise.+ if flag (wai-1) || flag (wai-2)+ buildable: False+ else+ build-depends:+ wai-websockets >= 3.0,+ warp >= 3.0,+ wai >= 3.0,+ websockets >= 0.9++ ghc-options: -threaded -Wall -with-rtsopts=-N