About the crash course.

85 views
Skip to first unread message

Aistis Raulinaitis

unread,
Nov 14, 2012, 2:50:07 PM11/14/12
to ha...@googlegroups.com
Hi all!

I think I might have found a mistake, should be an easy fix.

On page (http://www.happstack.com/docs/crashcourse/RqData.html) it seems that the "Handling missions" source code link actually downloads the "File uploads" source file.

Great tutorial! Thanks so much for the great work!

Aistis Raulinaitis

Jeremy Shaw

unread,
Nov 14, 2012, 3:09:41 PM11/14/12
to HAppS
Thanks! Fixed!




Aistis Raulinaitis

--
You received this message because you are subscribed to the Google Groups "HAppS" group.
To view this discussion on the web visit https://groups.google.com/d/msg/happs/-/YsZuCYarddcJ.
To post to this group, send email to ha...@googlegroups.com.
To unsubscribe from this group, send email to happs+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/happs?hl=en.

Luis Valencia

unread,
Dec 8, 2012, 3:51:29 AM12/8/12
to ha...@googlegroups.com
Another error is under the section 3 of the tutorial "Templating for HTML and Javascript" subsection "HSP and internationalization (aka, i18n)" section, at the end there "here" link links to TemplatesHSP.hs but it should link to TemplatesHSPI18n.hs

and a suggetion for section 4 "Parsing request Data from QUERY_STRING, cookies and request body" subsection "file uploads"

I notice you guys mention saving files but don't do it, when It would be a two line edit to add in the example and probably very beneficial to the end user.
I haven't gone through the rest of the tutorial, yet, maybe you show it later on, but you do make mention of the renameFile command, so here's what it would look like if you did want to save a file. The revised post method would look like this

post :: ServerPart Response                                                       
post =                                                                            
   do r <- lookFile "file_upload"                                                 
      saveFile r                                                                  
      ok $ toResponse $                                                           
         html $ do                                                                
            B.head $ do                                                           
               title "Post Data"                                                  
            B.body $ mkBody r                                                     
   where                                                                          
      saveFile (tmpFile, uploadName, contentType) = liftIO $ renameFile tmpFile $ "./"++uploadName
      mkBody (tmpFile, uploadName, contentType) = do                              
         p (toHtml $ "temporary file: " ++ tmpFile)                               
         p (toHtml $ "uploaded name: " ++ uploadName)                             
         p (toHtml $ "content-type: " ++ show contentType)    

where the bolded parts are the revisions and if you need an explanation as to why it works, it's really simple, liftIO has type signature
liftIO :: Control.Monad.IO.Class.MonadIO m => IO a -> m a

and from what I can tell ServerPart in the ServerPart Response signature accepts any of the following (Control.Monad.MonadPlus m, WebMonad Response m, ServerMonad m,FilterMonad Response m, Control.Monad.IO.Class.MonadIO m) in form m a where a is a Response. Well, clearly liftIO fills that type-sig requirement.

just a thought.

Luis Valencia

unread,
Dec 9, 2012, 2:27:51 AM12/9/12
to ha...@googlegroups.com
Another error section 4 subsection "other uses of checkRq" in the code RqDataCheckOther.hs
you're gonna get an error on compilation that reads:

    Ambiguous type variable `a0' in the constraints:
      (Num a0) arising from the literal `1' at rqDataCheckOther.hs:17:56
      (Ord a0)
        arising from a use of `inRange' at rqDataCheckOther.hs:17:48-54
      (Show a0)
        arising from a use of `inRange' at rqDataCheckOther.hs:17:48-54
      (Happstack.Server.Internal.Types.FromReqURI a0)
        arising from a use of `lookRead' at rqDataCheckOther.hs:17:24-31
    Probable fix: add a type signature that fixes these type variable(s)
    In the first argument of `inRange', namely `1'
    In the second argument of `checkRq', namely `(inRange 1 10)'
    In the first argument of `getDataFn', namely
      `(lookRead "i" `checkRq` (inRange 1 10))'

that's because we're giving it any type instance of 1 and 10 to Ord so it doesn't know what it is, it can be any type with a Num instance
so as advised in section 3 subsection 4 sub-subsection 3 "Ambiguous Types" we need an explicit type.
Revised function should read:

oneToTenPart :: ServerPart String                                                 
oneToTenPart =                                                                    
    do r <- getDataFn (lookRead "i" `checkRq` (inRange (1::Int) (10::Int)))       
       case r of                                                                  
         (Left e) ->                                                              
             badRequest $ unlines e                                               
         (Right i) ->                                                             
             ok $ "You picked: " ++ show i

where the bolded parts are the changes.
also idk if there's an actual thread where to post these kinds of errors, please say so if there is...

Luis Valencia

unread,
Dec 9, 2012, 4:13:00 AM12/9/12
to ha...@googlegroups.com
section 4 sub-section 7 "Looking up parameters" the code links to "RqDataError.hs" should link to RqDataOptional.hs


On Wednesday, November 14, 2012 11:50:07 AM UTC-8, Aistis Raulinaitis wrote:

Jeremy Shaw

unread,
Dec 10, 2012, 4:58:44 PM12/10/12
to HAppS
Thanks!

I have updated the mismatched source links. Clearly I need to change how those links are created so that happens automatically and correctly :) 

The issue with 'inRange' is odd. Something must have changed with defaulting in recent versions of GHC because that example used to work. I do have a target that automatically checks that all the examples actually compile -- and that used to build correctly (but now fails). In any case, I updated the code so it builds again.

Regarding the saving of files.. I probably should include some code showing how best to do it. In part, because the example code you provided is, in fact, pretty unsafe. if the user provided an 'upload name' like "../../etc/shadow" they could potentially overwrite your password database and gain root access. A more correct solution would probably use the 'combineSafe' function we provide here:

http://happstack.com/docs/happstack-server-7.0.2/doc/html/happstack-server/Happstack-Server-FileServe-BuildingBlocks.html#v:combineSafe

Thanks for the feedback!
- jeremy



To view this discussion on the web visit https://groups.google.com/d/msg/happs/-/OfdiOX_Bt7sJ.

Luis Valencia

unread,
Dec 12, 2012, 5:34:14 PM12/12/12
to ha...@googlegroups.com
That was great turnaround!

Yeah I don't claim to be an expert on these kind of operations using Haskell, so far I've only really used it for answer project Euler problems and golfing the code. I would love to see an end to end example using combineSafe, namely, because I didn't know that method existed and the problem you mentioned is clearly a security issue, so I was just doing what I could with what I know.

Thanks for fixing the issues Jeremy, as I go through the rest of the crash course, I'll point out any other errors I may encounter.

Luis Valencia

unread,
Dec 12, 2012, 6:47:58 PM12/12/12
to ha...@googlegroups.com
Section 4 subsection "Simple Cookie Demo" the code on the site doesn't accurately reflect the contents of the CookieCounter.hs file

        addCookie Session (mkCookie "requests" (show (requests + 1)))

should read

addCookie Session (mkCookie "requests" (show (requests + (1::Int))))

so it's just a consistency issue.

On Wednesday, November 14, 2012 11:50:07 AM UTC-8, Aistis Raulinaitis wrote:

Jeremy Shaw

unread,
Dec 12, 2012, 7:07:17 PM12/12/12
to HAppS
how odd. Those .hs files are automatically extracted from the crash course. Apparently I must have accidentally edited the extracted code in a fit of stupidity. Since the extract code was newer than the source, it didn't get updated when I reran the build process. Anyway, I cleaned up in the imports for that as well. 

Thanks!
- jeremy


--
You received this message because you are subscribed to the Google Groups "HAppS" group.
To view this discussion on the web visit https://groups.google.com/d/msg/happs/-/j24ymENltRsJ.

Luis Valencia

unread,
Dec 13, 2012, 4:36:16 PM12/13/12
to ha...@googlegroups.com
In section 5 subsection "serving a single file", there's a very misleading piece of code, I say misleading because the quantifier "like" was used for the example but the actual code is wrong

serveFile guessContentTypeM "/path/to/photos/photo_medium.jpg"

what this is going to do is generate an error because guessContentTypeM has a type signature Monad m => MimeMap -> FilePath -> m String
and it's going to complain that you're giving it a filePath, what it should read is:

serveFile (guessContentTypeM mimeTypes) "/path/to/photos/photo_medium.jpg"

which is actually really freaking cool, since serveFile expects (FilePath -> m String) -> FilePath and returns a Response type.
Sure enough (guessContentTypeM mimeTypes) returns a (FilePath -> m String).
Idk if it falls within the purview of Happstack to explain higher order functions, but it wouldn't be the worst thing to make a light mention instead of putting outright wrong code snipplets.

Incidentally, idk if you can answer this (or anyone can for that matter), if a MimeMap is supposed to be a defined as

type MimeMap =  Map String String

why is it that if we try to write our own, for example

import Data.Map (Map)
import qualified Data.Map as Map

main = serveFile (guessContentTypeM $ Map.fromList [("gif","image/gif"), ("jpe","image/jpeg"),("jpeg","image/jpeg"),("jpg","image/jpeg")])  "/path/to/photos/photo_medium.jpg"

will generate an error like

Couldn't match expected type `MimeMap' with actual type `Map k0 a0'
    In the return type of a call of `Map.fromList'

I mean I understand that fromList has a sig  Ord k => [(k, a)] -> Map k in which Map.fromList is actually being pulled from containers-0.5.1.0:Data.Map.Base

meaning that it doesn't equivocate Map String String with Data.Map.Base.Map String String when it tries to resolve it? is it just a namespace collision issue with the imports? I haven't been able to resolve this..


On Wednesday, November 14, 2012 11:50:07 AM UTC-8, Aistis Raulinaitis wrote:

Jeremy Shaw

unread,
Dec 15, 2012, 10:31:28 PM12/15/12
to HAppS
Thanks!

The guessContentTypeM issue is probably becuase guessContentTypeM used to have a hardcoded list of mime-types. When I took over the project that was one of the things I eventually changed. But apparently this section of the crashcourse predates that change. I wish there were more ways to automatically test the documentation -- but ultimately.. it comes down to proof readers finding mistakes. So a big thanks to you!!

Regarding the Map.fromList issue.. This code works fine for me:

module Main where

import Data.Map (Map)
import qualified Data.Map as Map
import Happstack.Server

main = simpleHTTP nullConf $
       serveFile (guessContentTypeM $ Map.fromList [("gif","image/gif"), ("jpe","image/jpeg"),("jpeg","image/jpeg"),("jpg","image/jpeg")])  "/path/to/photos/photo_medium.jpg"

You mention that Map.fromList is coming from containers 0.5. But, perhaps happstack-server is built against containers 0.4? If so.. that could be the source of your issue. If you have two versions of containers pulled in, then you will have two different types named 'Map' pulled in -- you can not use them interchangeably..

- jeremy
--
You received this message because you are subscribed to the Google Groups "HAppS" group.
To view this discussion on the web visit https://groups.google.com/d/msg/happs/-/yBiAQkft1QoJ.

Luis Valencia

unread,
Dec 19, 2012, 2:46:28 AM12/19/12
to ha...@googlegroups.com
oh you're welcome, I dont mind debugging because when I saw this Framework I thought to myself "finally, web development doesn't have to be dirty" so I want this framework to succeed and get used, starting with me, and then eventually other people I will work with if/when  i do we development.
At any rate yeah our hunch about source import being wrong is correct, so for future reference if that issue comes up, someone asks you, or someone is read this, it's easily resolvable by explicitly stating the packages:

ghc --make -threaded serveFileTest.hs -package containers-0.4.2.1

David Fox

unread,
Dec 19, 2012, 6:43:16 AM12/19/12
to ha...@googlegroups.com, ha...@googlegroups.com
Great slogan, can we use it?

Sent from my iPhone
To view this discussion on the web visit https://groups.google.com/d/msg/happs/-/CvPdbptY5DkJ.

Luis Valencia

unread,
Jan 20, 2013, 5:54:33 PM1/20/13
to ha...@googlegroups.com
What slogan?
"finally, web development doesn't have to be dirty"
Yeah of course, compared to what I have to do at work, I feel that this is a much more elegant and refined process that haphazardly slapping shit together with Python and Flask or Ruby and Rails. The only thing Happstack is lacking is an explicit agile process to follow.

Perhaps a real world case study on how you should go about building a website end to end, database integrations, you know Real World stuff.
For example, a juxtaposition, Real World Haskell is a great starting point against other Haskell books like Learn you Haskell for a greater good, mainly because the former is actually useful. What needs to happen is "Real World Happstack"

The short answer is yes, if you really want to.
Also sorry for the slow turnaround, work has been hectic.

Luis Valencia

unread,
Jan 21, 2013, 3:56:37 AM1/21/13
to ha...@googlegroups.com
in section 6 subsection "Hello Form!", down by the AppError section the sentence "Instead of have one error type for all the forms" should read "Instead of having..."

at the very end the sentence "There is nothing reform specific about."
should read "There is nothing reform specific about it."

Also the crash course is ambiguous in that we don't actually use provenPostForm, where is this supposed to go in actual use?
I mean I get that we separated validation and views so I guess the implication is we use applicative functors to do this, however I feel that there's definitely a lacking explanation as to what should happen when we want to actually use it, or better yet, in what context or instance would we want to use it.


On Wednesday, November 14, 2012 11:50:07 AM UTC-8, Aistis Raulinaitis wrote:

Aistis Raulinaitis

unread,
Jan 31, 2013, 10:27:00 AM1/31/13
to ha...@googlegroups.com
Found another one. :) It's on: http://happstack.com/docs/crashcourse/RqData.html#rqdatacheckrqmore
I don't think that the example needs the data Vote line.
Reply all
Reply to author
Forward
0 new messages