Code Monkey home page Code Monkey logo

scotty's People

Contributors

5outh avatar abailly avatar abhinav avatar adamflott avatar akii avatar bardurarantsson avatar bgamari avatar chessai avatar chris-martin avatar clockfort avatar clrnd avatar dmjio avatar echaozh avatar edofic avatar ehamberg avatar friedbrice avatar fujimura avatar fumieval avatar hakujin avatar jfraudeau avatar madnight avatar ocramz avatar potomak avatar ryanglscott avatar sebastian-philipp avatar sol avatar sordina avatar tfausak avatar tusharad avatar xich avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scotty's Issues

Better Type-Safety when routing.

I've set up a project with samples of how routing is accomplished in the major players (Happstack, Snap and Yesod). The programs should produce more-or-less identical results. My aim is to get your feed back on how routing in Scotty should look as to be as concise and type-safe as possible.

routing-comparison

Line Up:

Observations

  • Happstack was the most concise (39 lines total). This could actually be 2 lines shorter if you were using the ScopedTypeVariables language option.
  • Yesod's use of TemplateHaskell did not make it more concise to use.
  • Yesod's use of TemplateHaskell made it impossible to Overload a route with multiple handlers each taking different inputs.
  • Yesod didn't have a canonical implementation of query parameters.
  • Snap didn't support overloading a route with multiple handlers. If a handler failed the request was rejected, it was not passed off for another handler to try.
  • Snap doesn't have any type limitations, if data of the wrong type gets into the handler exceptions occur.
  • Scotty, while not actually type-safe, will handle improperly typed data in the form of fail-over. Overloading routes by type is possible.
  • Scotty handles optional search parameters strangely. If a search parameter is missing, calling for it produces an exception instead of a Maybe.

Overall, I'm a huge fan of Happstack's view of routing and they are the target to beat. The other two frameworks both have problematic routing patterns which I believe Scotty already beats.

Possible Pattern

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}

import           Web.Scotty
import           Control.Monad.Trans
import           Data.Time
import           Data.Text.Lazy         as LT

main :: IO ()
main = scotty 3000 $ do
    get "/" getHome
    get "/time" getTime
    get "/add/?/?" [getAddNum, getAddText]
    get "/search" getSearch

getHome :: ActionM ()
getHome = html "<h1>Hello World!</h1>"

getTime :: ActionM ()
getTime = do
        t <- lift $ getCurrentTime
        html $ LT.pack $ show t

getAddNum :: Integer -> Integer -> ActionM ()
getAddNum n1 n2 = html $ LT.pack $ show (n1 + n2)

getAddText (w1::Text) (w2::Text) = html $ LT.concat [w1, w2]

getSearch :: ActionM ()
getSearch = do
        contact <- (param "contactId") `rescue` (\x -> return "Not Found")
        company <- (param "companyId") `rescue` (\x -> return "Not Found")
        html $ LT.concat [ "<ul>"
                         , "<li>Contact ID: ", (contact::Text), "</li>"
                         , "<li>Company ID: ", (company::Text), "</li>"
                         , "</ul>"
                         ]

This is my first thought on a possible pattern for Scotty's routing. This would remove the need to call param inside a handler's Monad for path parameters. It would preserve the ability to overload a route by types. It would also not conflict with the current routing pattern in Scotty, so it should be possible to keep backwards compatibility.

I haven't yet worked out how to implement this pattern, I'm hoping to get feedback about interest in this pattern before doing the hard part.

Network.Wai.Middleware.MethodOverride not supported

Network.Wai.Middleware.MethodOverride allows you to override the restful method of an HTML form. For example, you could update a field and handle it as a PUT in your routes with the following HTML:

<form method="POST" action="/update">
    <input type="hidden" name="_method" value="PUT" />
    <input type="text" name="nickname" />
    <input type="submit" />
</form>

The following Scotty application fails to accept overridden PUT requests and instead interprets them as POST. In other words, the middleware fails to have the results of the rewritten request passed to the rest of the application:

{-# LANGUAGE OverloadedStrings #-}

import Prelude hiding (head, id)
import Web.Scotty hiding (body, text)
import Network.Wai.Middleware.RequestLogger  (logStdoutDev)
import Network.Wai.Middleware.MethodOverride (methodOverride)
import Data.Monoid ((<>))

main = scotty 3030 $ middleware methodOverride >> middleware logStdoutDev >> home

home = do
  get "/" $ html $ homepage $ []
  put "/" $ param "nickname" >>= html . homepage

homepage [ n ] = str n
homepage   _   = str ""

title = "Network.Wai.Middleware.MethodOverride Test"

str s = "<html><head><title>" <> title
      <> "</title></head><body><h1>" <> title
      <> "</h1><form method='post' action='/'><input type='hidden' name='_method' value='PUT' />"
      <> "<input type='text' name='nickname' value='" <> s
      <> "' /><input type='submit' value='Submit Test' /></form></body></html>"

Odd behaviour with static policy using addBase

I wanted to serve static files from src/static, under /static (so GET /static/some-file would look for src/static/some-file), so I did this:

let p = noDots >-> hasPrefix "/static" >-> addBase "src"
staticPolicy p

This didn't quite do what I expected:

tryPolicy p "/static/some-file" == Just "/static/some-file"

I think it's because of </> in System.FilePath; it seems it just returns the second argument if it starts with a slash:

"src" </> "/static/some-file" == "/static/some-file"

However curl and firefox both seem to always add this leading slash in the request URI.

Should addBase use ++ rather than FP.</>?

Custom 404 pages

I'd like a method to easily specify a custom 404 page. It looks like the 404 page is generated by notFoundApp right now, and there's no way to override that without touching scotty code. I propose a new function, notFound:

notFound :: ActionM () -> ScottyM ()

-- usage:
notFound $ text "404!"

I can think of a couple of ways to implement this.

  1. Allow arbitrary regular expressions in url paths, and then define notFound as: notFound = addRoute GET "*" (although it should work for any method, not just GET). We would also need to figure out how to trigger that route last, after every other route has been checked.

  2. Add a new notFoundApp field in the ScottyState datatype. Then we could define notFound as:

    data ScottyState = ScottyState { middlewares :: [Middleware]
                                   , routes :: [Middleware]
                                   , notFoundApp :: Middleware
                                   }
    
    notFound :: ActionM () -> ScottyM ()
    notFound action = Ms.modify (\(ScottyState ms rs _) -> ScottyState ms rs (runAction blankEnv action))
    

If either of these sound satisfactory, I can submit a patch. Either way, I think notFound and regex support are worth adding.

Kill request handeling thread on abort

When a user aborts an http request, the thread handeling the request continues to run. While it is sometimes desirable, this is often useless and wastes resources. Would it be possible to allow canceling/killing of requests after the http-connection was aborted by the user?

With Wai Gzip middleware?

Hi!

Trying to serve a large text file via Scotty had me looking at Network.Wai.Middleware.Gzip and while this example from yesodweb works:

 {-# LANGUAGE OverloadedStrings #-}
 import Network.Wai
 import Network.Wai.Handler.Warp (run)
 import Network.Wai.Middleware.Gzip (gzip, def)
 import Network.HTTP.Types (status200)

 application _ = return $
   responseLBS status200 [("Content-Type", "text/plain")] "Hello World"

 main = run 80 $ gzip def application

My attempt to similarly add the middleware in Scotty fails so clearly I'm doing it wrong:

  {-# LANGUAGE OverloadedStrings #-}
 import Web.Scotty
 import Network.Wai.Middleware.RequestLogger
 import Network.Wai
 import Network.Wai.Middleware.Gzip (gzip)
 import qualified Data.Text.Lazy as T
 import Data.Text.Lazy.Encoding (decodeUtf8)

 main :: IO ()
 main = scotty 80 $ do
 middleware gzip
 middleware logStdoutDev
 get "/"  $ file "index.html"

 > sudo runghc testServer.hs

 testServer.hs:19:16:
     Couldn't match expected type `Application'
                 with actual type `Network.Wai.Middleware.Gzip.GzipSettings'
     Expected type: Middleware
       Actual type: Network.Wai.Middleware.Gzip.GzipSettings
                    -> Middleware
     In the first argument of `middleware', namely `gzip'
     In a stmt of a 'do' block: middleware gzip

Just looking for some guidance on the right way to add this Middleware. Thanks in advance!

About the monads in ScottyT and ActionT

ScottyT and ActionT share the same monad as their type parameters. From the code, I see that ScottyState needs the monad to be used in the routes, as they're constructed from ActionT. However, the monad wrapped by the ScottyT does not need to be the same monad. It's only used at initialization, and have nothing to do with ActionT. Can ScottyT be modified to use too different monads, 1 for routes, and 1 for initialization? Or perhaps dropping the second monad altogether and fix it as Identity? It looks like it cannot do much at initialization, and one can always initialize before calling scottyT.

Also, why not allow Scotty specific middlewares around actions and inside WAI ones? Say if I want to parse the cookies in the request before every action, and store the results in the monad to be used in each ActionT, there is no convenient way to do that with WAI middlewares.

Get all param-s

Currently there is only param which does a lookup in ActionEnv(getParams). But it is in a hidden module and one should parse POST request body manually to get values.

Unable to compile scotty-0.4.1

cabal install scotty ~
Resolving dependencies...
Configuring scotty-0.4.1...
Preprocessing library scotty-0.4.1...
Building scotty-0.4.1...
[1 of 5] Compiling Web.Scotty.Util ( Web/Scotty/Util.hs, dist/build/Web/Scotty/Util.o )
[2 of 5] Compiling Web.Scotty.Types ( Web/Scotty/Types.hs, dist/build/Web/Scotty/Types.o )
[3 of 5] Compiling Web.Scotty.Action ( Web/Scotty/Action.hs, dist/build/Web/Scotty/Action.o )
[4 of 5] Compiling Web.Scotty.Route ( Web/Scotty/Route.hs, dist/build/Web/Scotty/Route.o )

Web/Scotty/Route.hs:18:30:
Module Data.Monoid' does not export(<>)'
cabal: Error: some packages failed to install:
scotty-0.4.1 failed during the building phase. The exception was:
ExitFailure 1

What version of Data.Monoid should be installed?

ghci ~
GHCi, version 7.0.4: http://www.haskell.org/ghc/ :? for help

wai-middleware-static-0.4.0.3 + wai-2.1.0 build failure

I have experienced troubles while installing scotty-0.7.0.0 and wai-middleware-static-* (any version) with already installed lastest wai-2.1.0 and warp-2.1.1.2:

rejecting: wai-middleware-static-0.4.0.2/installed-9a2...,0.4.0/installed-4ba... 
(conflict: warp => __wai==2.1.0__/installed-c23..., wai-middleware-static => __wai==2.0.0__/installed-f87...)
rejecting: wai-middleware-static-0.4.0.2, 0.4.0.1, 0.4.0 
(conflict: warp => __wai==2.1.0__/installed-c23..., wai-middleware-static => __wai>=2.0.0 && <2.1__)                                                              
rejecting: wai-middleware-static-0.3.2, 0.3.1, 0.3.0, 0.2.2, 0.2.1, 0.2.0, 0.1.2, 0.1.1, 0.1.0, 0.0.1 

Any particular reason we can't bump wai upper bound (like wai >= 2.0.0 && < 2.2) in wai-middleware-static package?

Get Request body is always Empty

When I send an ajax request to a url like

$.ajax({ url: "/myurl",
         type: "GET", 
         data: JSON.stringify([0,["hello"]]), 
         dataType: "json", 
         success : function(d) {alert(JSON.stringify(d));}})

and then in the route handler for myurl I try to get the json as

js <- jsonData

But It was returing json parse error (Assuming js having the correct type).

So I checked the body of the request by

bd <- body

and to my surprise it was returning Empty.

If I check params I can see my data in there but I can't get it in the body.

Is this a bug? Because above seems to work for put requests.

Retrieving Auth headers on HTTP action

Is there any way I can retrieve Authentication headers from a request?
Like Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== for example.

(this is not really a bug, but more a general question)

Security: update the dependency to Warp > 2.0.3.3

From the web-devel mailing list:


We received a security report yesterday of a regression in the Warp 2.0
series[1] related to Slowloris protection. Please see the issue for
details. The important information is:

  • Versions prior to 2.0 are not affected.
  • Updating to 2.0.3.3 solves the problem.

Yesod users: I've just released a new version of yesod-platform which
updates the warp dependency.

If there are any questions, please let me know.

[1] yesodweb/wai#231

Exposing Parsable

I was wondering if you had a good reason to not expose the internals of Parsable? I'm porting a project from yesod to scotty and I have some URL captures that wrap already-parsable types like Text that I'd like to define Parsable instances for.

Rescue jsonData errors

I'm not sure if I'm going about this the right way, but when I have an invalid json structure I will get the error <h1>500 Internal Server Error</h1>jsonData: no parse from Scotty. Is there a way that I can change the error message so that I could perhaps print out the body that is causing the error? I looked into the rescue function but I couldn't figure out how to use it to catch the jsonData error that is raised. Also, I'm new to Haskell and Scotty, so I might be missing something obvious.

reqHeader should return its result in Maybe

Hej!

I believe that the return type of reqHeader should be wrapped inside a Maybe inside of throwing an Internal Server Error. This is how most Haskell web frameworks deal with this and it makes more sense to be able to easily handle a missing header in the application.

If we want to throw an error here we can still do it! :-)

Cheers,
Vincent

Support regexes for routes

This is a ticket for adding support for the following syntax:

get (mkRegex "^\\d+$") $ html "The url has all digits!"

This could be done by generalizing path via a Routeable typeclass:

class Routeable a where
  matchReq :: a -> Text -> Bool

instance Routeable Text where
  matchReq pat req = -- match plain route

instance Routeable Regex where
  matchReq pat req = -- match regex

Project Starter kit

I made a simple project starter kit for people looking to get going fast.

Used blaze-from-html to turn HTML5 boilerplate into blaze code. Added a twitter bootstrap theme, and setup the cabal file for heroku deployment. Would be nice to add oauth, acid-state, and fay integration as well. Not sure how heroku would handle acid-state, maybe add a postgres option as well.

http://scottystarter.herokuapp.com

Code is here if interested: https://github.com/dmjio/scotty-starter

"Header not in scope" in examples/basic.hs

Just taking a look at the example code, running examples/basic.hs errors out with:

examples/basic.hs:41:9:
Not in scope: header' Perhaps you meant one of these: head' (imported from Prelude),
`T.head' (imported from Data.Text.Lazy)

I was able to get it running by just using setHeader instead of header - is this a correct change or am I missing something?

Error handling

Hi. Thank you for an awesome framework, I'm enjoy it very much!

However, I have hard time dealing with error handling for a couple of reasons.

First, raise takes Text argument, it would be nice to define and pass custom error types.

Second, there are no single place to handle errors (thrown with raise) from all handlers. It is possible to pass handler as an argument to scottyT, but I guess it is intended for other purposes, and it is not clear how to build Response from scratch.

I really would like the ability to throw arbitrary values, and converting it to actions in a single place. For example:

get "/items/:name" $ do
    ...
    raise $ NotFound itemId
    ...

delete "/items/:name" $ do
    ...
    raise Forbidden
    ...

-- Then somewhere
handle :: Err -> ActionM ()
handle NotFound id = do
    status status404
    json $ jsonNotFound id
handle Forbidden = do
    status status403
    json jsonForbidden

I'm still new to Haskell, and probably I'm just not understand something, then feel free to close the issue. I will appreciate some explanations though.

Thank you! :)

ByteString vs Text

Need to carefully examine where it is appropriate to use ByteString and where it is appropriate to use Text. For instance, headers currently return Text values... but are all HTTP headers encode-able as Text?

redirect query parameters

It would be nice if either the redirect function was query-string-parameter-aware by default, or if there was an alternate redirect function that did the same.

It's possible to implement currently in scotty, but it's gross, and causes a lot of code duplication, especially if you need variable existence error checking (which would now have to be rescued in two places to avoid 500 errors), etc.

tl;dr:
I propose that this code is dumb:

main = scotty 3000 $ do
        get "/" $ do
                var1 <- param "var1"
                var2 <- param "var2"
                redirect $ mconcat["/foo?var1=", var1, "&var2=", var2]

and would prefer the ability to do:

main = scotty 3000 $ do
        get "/" $ do
                redirect "/foo" --foo handles all param functionality

Applicative Instance for ActionT a

An Applicative instance for ActionT a would be really useful, as you could construct data-records like so:

Foo <$> param "bar" <*> param "baz"

You are awesome. Thanks

Just wanted to say thanks for making this. I kept wishing miku was more standard, and hack was used more, and here comes scotty built on WAI and Warp! Perfect! Thanks.

issue installing latest versions

With a clean install of the newest haskell platform for OSX (2012.4.0.0) I get some weird errors from cabal when trying to install scotty:

~ โž” cabal install scotty
Resolving dependencies...
In order, the following would be installed:
byteorder-1.0.3 (new package)
data-default-0.5.0 (new package)
date-cache-0.3.0 (new package)
regex-base-0.93.2 (reinstall) changes: mtl-2.1.1 -> 2.1.2
regex-posix-0.95.2 (reinstall)
regex-compat-0.95.1 (reinstall) changes: regex-posix-0.95.1 -> 0.95.2
stringsearch-0.3.6.4 (new package)
unix-time-0.1.4 (new package)
fast-logger-0.3.1 (new package)
wai-logger-0.3.0 (new package)
zlib-bindings-0.1.1.2 (new package)
zlib-conduit-0.5.0.3 (new package)
wai-extra-1.3.0.5 (new package)
scotty-0.4.6 (new package)
cabal: The following packages are likely to be broken by the reinstalls:
haskell-platform-2012.4.0.0
regex-posix-0.95.1
MissingH-1.2.0.0
Use --force-reinstalls if you want to install anyway.

Notice near the bottom it's saying that haskell-platform will broken by the install. Any idea what could cause this?

Middleware, actions and Vault

I've tried to extract my code and put it into middleware, but can't quite wrap my head around Vault thing. It is quite cryptic by itself with that Key a and having to fetch a request, then extract vault record to do a thing not adding to simplicity at all. I think we can do better than that. (:

I propose adding some helpers like getVault/putVault to work in a middleware context and lookVault for actions to operate on middleware-provided values.

I will try to make a patch if i will suceed in my authMiddleware musings...

Cookies

Write an example (and possibly scotty extension or middleware) for setting and reading cookies.

clicker.hs example doesn't work

I use scotty-0.4.6. The clicker examples gives this error:

clicker.hs:23:8:
    Could not find module `Web.Scotty.Util'
    it is a hidden module in the package `scotty-0.4.6'
    Use -v to see a list of the files searched for.

Which make sense since clicker.hs imports a hidden module.

liftIO broke: No instance for (MonadIO (scotty-0.7.2:Web.Scotty.Types.ActionT T.Text IO))

Building example/basic.hs GHC 7.8.2 Windows/Mys/mingw. Example of error:

$ ghc -o basic -package-db ../$DB basic.hs
[1 of 1] Compiling Main             ( basic.hs, basic.o )

basic.hs:100:16:
    No instance for (MonadIO
                       (scotty-0.7.2:Web.Scotty.Types.ActionT T.Text IO))
      arising from a use of `liftIO'
    In the expression: liftIO
    In a stmt of a 'do' block: msg <- liftIO $ liftM fromString getLine
    In the second argument of `($)', namely
      `do { msg <- liftIO $ liftM fromString getLine;
            text msg }'

wai-eventsource integration (turn a wai Application into ActionT)

The problem I've encountered is that it's impossible to turn a wai Application into ActionT. Examples when we might need this includes both wai-eventsource and wai-websockets enabled applications, but lets consider the first one for a moment:

get "/:foo/updates" $ do 
  application (eventSourceAppChan myEventStream)

where application function is impossible to implement because ScottyResponse do not allow to return a plain old wai Response. The workaround is to set middleware with interceptor routing, which is pretty awkward.

ActionM and ScottyM as monad transformers

Hi Andrew,

@shapr is using your Scotty library for the Google Summer of Code Project 2012 "A multi-user browser-based ghci".

In particular, we want to create a Haskell interpreter that can be controlled via a HTTP interface, using Scotty. We use the GHC API for implementing the interpreter functionality. One problem is that the GHC API uses a custom monad to keep track of the interpreter state. The only way to integrate this persistent state with the Scotty framework is to turn the ActionM and ScottyM monads into monad transformers.

This would be a pervasive change, every function would have to be changed to mention the base monad. Examples:

param  :: (Monad m, Parsable a) => T.Text -> ActionT m a
body   :: Monad m => ActionT m BL.ByteString

scotty :: MonadIO m => Port -> ScottyT m () -> m ()

(That said, I think it is possible to remove many of the annoying Monad m => contexts by using the Cont monad or my operational package.) I'm happy to perform these changes myself and submit a patch, but I am not sure whether this is a design path you want to take. After all, it does complicate the interface significantly and may not coincide with your goal of keeping Scotty simple to use.

Unfortunately, this change is essential for our use of Scotty; we will have to use another web framework otherwise.

It would be great if you would let us know your decision soon, so the GSoC project can move on smoothly.

Can't use parameters from a query string

From the examples:

    -- Using a parameter in the query string. If it has
    -- not been given, a 500 page is generated.
    get "/foo" $ do
        v <- param "fooparam"
        html $ mconcat ["<h1>", v, "</h1>"]

Trying GET /foo?fooparam=hello, gives me a 404.

Should static policies include noDots by default?

I can't think of a good reason why you'd want to be able to request any file on the same disk as a running web application; it seems better to be secure by default, and to optionally remove the noDots guard, rather than require users to remember to put it in.

String-type independency

I like to implement this if you like =) I thought about this, because some libraries use Bytestring, others lazy Bytestring ... and you have to constantly do the conversion back and forth.

I'm just started to work and want to hear any suggestions...

And scotty will be

  • is template-language agnostic. Anything that returns a String (ByteString,Text,String and others) value will do.

Also similar thing exist in Yesod http://hackage.haskell.org/packages/archive/yesod-core/1.1.1.1/doc/html/src/Yesod-Content.html#ToContent

class ScottyString a where
  toContent :: a -> Content
  toText :: a -> T.Text
  fromScotty :: T.Text -> a

instance ScottyString B.ByteString where
  toContent bs = ContentBuilder (fromByteString bs)

instance ScottyString BL.ByteString where
  toContent bs   = ContentBuilder (fromLazyByteString bs)

instance ScottyString T.Text where
  toContent = toContent . encodeUtf8

instance ScottyString String where
  toContent = toContent . B.pack
html :: (ScottyString a) => a -> ActionM ()
html t = do
    header "Content-Type" "text/html"
    MS.modify $ setContent $ toContent t

Param Lists/Maps

PHP and other languages support sending Lists via GET and POST using the Syntax

param[]=a&param[]=b&param[]=c

You could also implement sending Maps:

param[key1]=val1&param[key2]=val2

Scotty would get two new functions

paramAsMap :: Parsable p => Text -> Action (Map Text p)
paramAsList :: Parsable p => Text -> Action [p]

mtl and transformers version bumps

Hi,

could you please bump the "mtl" version max to "<2.3" and the transformers max to "<0.5"?

(I've only compile-tested, but there hopefully shouldn't be any surprises since the changes in transformers and mtl have been pretty small; mostly just additions.)

Disallow `\r\n` in Header text

As the following program illustrates, it is possible to set more than one HTTP header by passing a string containing \r\n to setHeader, possibly leading to duplicate headers:

{-# LANGUAGE OverloadedStrings #-}

import Web.Scotty

main = scotty 7000 $ get "/" $ do
         addHeader "X-Foo" "Hey\r\nContent-Type: bla"
         text "..."
$ curl -i 'http://localhost:7000'
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Thu, 22 May 2014 20:05:28 GMT
Server: Warp/2.1.5.1
Content-Type: text/plain
X-Foo: Hey
Content-Type: bla

...

Of course, data should ideally be sanitized before it reaches all they way to setHeader, but it would nonetheless be good to disallow this at the setHeader level, or possibly even lower-level than that.

support uploads

Enhancement: support file uploads/multipart form posts. If scotty does already you could add a example.

Captures are not url-decoded

There doesn't seem to be any use of decodePathSegments or urlDecode in Scotty so far.

This may make sense from the point of view of leaving decoding up to the user and allowing the specification of the route exactly, but there will probably be some surprise when the user tries to match:

get "/users/:user" $ do
  user <- param "user"
  if user == "John Doe" then (html "Hi John!")
  else next

...but instead needs to test against "John%20Doe".

Similarly, if this is used for captured text, then there will be %xx codes throughout the HTML, DB, etc.

I'm not sure the best way to go about making the change for this so that it behaves sanely for RoutePatterns other than Keyword (probably leave decoding up to the user), but for Keword types, the best option I can see would be to pass another parameter to matchRoute - the result of decodePathSegments $ rawPathInfo req.

This would eliminate the need for explicit recursion in matchRoute as well as passing decoded path segments to be matched against in order to have predictable behaviour.

Other RoutePattern types should probably be left up to the user to decode as urlDecoding the entire path without splitting on "/" would lead to the opportunity to insert extra slashes through %2F and similar concerns, breaking general matching logic.

Fun note - I came across this when trying to perform an HTML injection as a test by allowing direct insertion of url keywords with preEscapedLazyText. It turns out this is harder than it should be.

Example Code:

route :: StdMethod -> RoutePattern -> ActionM () -> Middleware
route method path action app req =
    if Right method == parseMethod (requestMethod req)
    then case matchRoute path (strictByteStringToLazyText $ rawPathInfo req) (fmap TL.fromStrict $ decodePathSegments $ rawPathInfo req) of
            Just captures -> do
                env <- mkEnv req captures
                res <- lift $ runAction env action
                maybe tryNext return res
            Nothing -> tryNext
    else tryNext
  where tryNext = app req
matchRoute (Keyword "/") _ []       = Just []
matchRoute (Keyword pat) _ segments = fmap catMaybes $ sequence $ zipWithLengthMatch go splitpat segments
  where
    splitpat = (drop 1 $ T.split (=='/') pat)

    go key val = test (T.uncons key)
      where
        test Nothing       | key == val = Just Nothing         -- Empty val must equal empty key
        test (Just (k,ks)) | k  == ':'  = Just $ Just (ks,val) -- Capture
                           | key == val = Just Nothing         -- No capture
        test _                          = Nothing              -- Fall back to nothing for all other cases

zipWithLengthMatch :: ( a -> b -> Maybe (Maybe c) ) -> [ a ] -> [ b ] -> [ Maybe (Maybe c) ]
zipWithLengthMatch _ []     []     = [ ]
zipWithLengthMatch f (x:xs) (y:ys) = f x y : zipWithLengthMatch f xs ys
zipWithLengthMatch _ _      _      = [ Nothing ] -- Fail

Examples are leaking memory

Here is a console session in which i torment minifier with ab -c 50 -n 100000:

wiz@moon:~/src/scotest$ ghc minifier.hs -O2
Linking minifier ...

wiz@moon:~/src/scotest$ ./minifier &
[1] 4631

wiz@moon:~/src/scotest$ Setting phasers to stun... (ctrl-c to quit) (port 3000)

wiz@moon:~/src/scotest$ free -m
total used free shared buffers cached
Mem: 3963 3027 936 0 402 1445
-/+ buffers/cache: 1178 2784
Swap: 0 0 0

wiz@moon:~/src/scotest$ free -m
total used free shared buffers cached
Mem: 3963 3898 64 0 402 1445
-/+ buffers/cache: 2050 1912
Swap: 0 0 0

wiz@moon:~/src/scotest$ free -m
total used free shared buffers cached
Mem: 3963 3909 54 0 379 1283
-/+ buffers/cache: 2246 1716
Swap: 0 0 0

wiz@moon:~/src/scotest$ free -m
total used free shared buffers cached
Mem: 3963 3916 46 0 368 1228
-/+ buffers/cache: 2318 1644
Swap: 0 0 0

wiz@moon:~/src/scotest$ fg
./minifier
^C

wiz@moon:~/src/scotest$ free -m
total used free shared buffers cached
Mem: 3963 2770 1192 0 368 1228
-/+ buffers/cache: 1172 2790
Swap: 0 0 0


JSON example is exhibiting the same messy behavior.

ghc 7.4, scotty 0.4.0

Parse query parameters on POST request

As of scotty-0.4.2, the get handler will decode parameters from the URL, while the post handler will not decode parameters from the request body.

I suggest to change this behavior. Whenever a post handlers receives a request whose body has MIME type application/x-www-form-urlencoded, it should decode the body and make the parameters available via the param function.

The reason is that web forms can be submitted with either GET or POST and it is preferable to handle these in a uniform way. Otherwise, if you want to change a form request from GET to POST, you have to rewrite your handler more significantly than just replacing the get for a post call.

Policy to control Middleware.Static caching

I'm using scotty + wai-middleware-static as a simple webserver for a web app, and changes to static files don't seem to be getting picked up when I refresh the browser.

If I change, for example, index.html, and refresh, I won't see the new index.html until I restart the server.

Clearing my browser's cache has no effect, so I think it's on the server side. The haskell source for my quite basic server is here:

https://gist.github.com/casey/7583355

Is there a way to control how scotty and wai-middleware-static cache static content? I would like to have all static files be reloaded from disk on every request, at least during development.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.