This article has multiple issues. Please helpimprove it or discuss these issues on thetalk page.(Learn how and when to remove these messages) (Learn how and when to remove this message)
|
| Yesod | |
|---|---|
| Original author | Michael Snoyman |
| Developers | Michael Snoyman, et al. |
| Initial release | 2010; 16 years ago (2010) |
| Stable release | |
| Written in | Haskell |
| Operating system | Cross-platform |
| Available in | English |
| Type | Web framework |
| License | MIT |
| Website | www |
| Repository | |
Yesod (Hebrew pronunciation:[jeˈsod];Hebrew:יְסוֺד, "Foundation") is aweb framework based on theprogramming languageHaskell for productive development of type-safe, representational state transfer (REST) model based (where uniform resource locators (URLs) identify resources, andHypertext Transfer Protocol (HTTP) methods identify transitions), high performance web applications, developed by Michael Snoyman, et al. It isfree and open-source software released under anMIT License.
Yesod is based on templates, to generate instances for listed entities, and dynamic content process functions, throughTemplate Haskell constructs to hostdomain-specific language (eDSL) content templates called QuasiQuotes, where the content is translated into code expressions bymetaprogramming instructions.[2]
There are also web-like languagesnippet templates that admit code expression interpolations, making them fully type-checked atcompile time.[3]
Yesod divides its functions in separatelibraries (database, html rendering, forms, etc.) so functions may used as needed.
Yesod uses themodel–view–controller (MVC)software design pattern for itsuser interfaces.
Yesod uses aWeb application interface (WAI),[4] a type ofapplication programming interface (API), to isolateservlets,aka web apps., from servers, with handlers for the server protocolsCommon Gateway Interface (CGI),[5]FastCGI,[6]Simple Common Gateway Interface (SCGI),[7] Warp,[8]Launch (open as localURL to the default browser, closing the server when the window is closed),[9]
See ref.[10] Yesod requires a data type that instantiates themodel–view–controller classes. This is called thefoundation type. In the example below, it is named "MyApp".
TheREST model identifies a web resource with a web path. Here, REST resources are given names with an R suffix (like "HomeR") and are listed in aparseRoutes site map description template. From this list, route names and dispatch handler names are derived.
Yesod makes use ofTemplate Haskell metaprogramming to generate code from templates at compile time, assuring that the names in the templates match and everything typechecks (e.g. web resource names and handler names).
By inserting amkYesod call, this will callTemplate Haskell primitives to generate the code[11] corresponding to the route type members, and the instances of the dispatch controller classes as to dispatchGET calls to routeHomeR to a routine named composing them both as "getHomeR", expecting an existing handler that matches the name.
"Hello, World!" program example based on aCommon Gateway Interface (CGI) server interface (the handler types have changed, but the philosophy remains):
{- file wai-cgi-hello.hs -}{-# LANGUAGE PackageImports, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}import"wai"Network.Waiimport"wai-extra"Network.Wai.Handler.CGI(run)-- interchangeable WAI handlerimport"yesod"Yesodimport"yesod-core"Yesod.Handler(getRequest)import"text"Data.Text(Text)import"shakespeare"Text.Cassius(Color(..),colorBlack)-- the Foundation typedataMyApp=MyApp-- sitemap template, listing path, resource name and methods accepted-- `mkYesod` takes the foundation type name as param. for name composition of dispatch functionsmkYesod"MyApp"[parseRoutes|/HomeRGET|]instanceYesodMyApp-- indentation structured CSS templatemyStyle::[Text]→CssUrlurlmyStyleparamStyle=[cassius|.boxborder:1pxsolid#{boxColor}|]whereboxColor=caseparamStyleof["high-contrast"]→colorBlack_→Color00255-- indentation structured HTML templatemyHtml::[(Text,Text)]→HtmlUrlurlmyHtmlparams=[hamlet|
<!-- indentation, or lack of it, under starting tags or commands ('$' prefix) describe the content or sequence tree structure --><!-- '.' or '#' prefixes in tags introduce css styled "class" or "id" attribute values --><!-- interpolation of haskell expressions follow the "shakespeare templates"#{expr} syntax --><p>Hello World! There are<span.box>#{length params} parameters</span>:$if null params<p>Nothing to list$else<ul>$forall param<-params<li>#{fst param}:#{snd param}|]
getHomeR::HandlerRepHtmlgetHomeR=doreq<-getRequestletparams=reqGetParamsreqparamStyle<-lookupGetParams"style"defaultLayout$do-- adding widgets to the Widget monad (a ''Writer'' monad)setTitle"Yesod example"toWidgetHead$myStyleparamStyletoWidgetBody$myHtmlparams-- there are ''run'' function variants for different WAI handlersmain=toWaiAppMyApp>>=run
# cgi testexportREMOTE_ADDR=127.0.0.1exportREQUEST_METHOD=GETexportPATH_INFO=/exportQUERY_STRING='p1=abc;p2=def;style=high-contrast'./wai-cgi-hello
See ref.[12][13] Yesod follows therepresentational state transfer model of access to web documents, identifying docs. and directories as resources with a Route constructor, named with an uppercase R suffix (for example, HomeR).
URL segment capture as parameter is possible specifying a '#' prefix for single segment capture or '*' for multisegment capture, followed by the parameter type.
-- given a MyApp foundation typemkYesod"MyApp"[parseRoutes|/HomeR-- no http methods stated: all methods accepted/blogBlogRGETPOST-- the '#' prefix specify the path segment as a route handler parameter/article/#ArticleIdArticleRGETPUT-- the '*' prefix specify the parameter as a sequence of path pieces/branch/*TextsBranchRGET-- to simplify the grammar, compound types must use an alias, eg. type Texts for ''[Text]''|]
dataRouteMyApp=HomeR-- referenced in templates as: @{HomeR}|BlogR-- in templates: @{BlogR}|ArticleRArticleId-- in templates: @{ArticleR myArticleId}|BranchRTexts-- in templates: @{BranchR myBranchSegments}
-- for "/ HomeR" -- no http methods stated ⇒ only one handler with prefix ''handler''handlerHomeR::HasRepst⇒Handlert-- for "/blog BlogR GET POST"getBlogR::HasRepst⇒HandlertpostBlogR::HasRepst⇒Handlert-- for "/article/#ArticleId ArticleR GET PUT"getArticleR::HasRepst⇒ArticleId→HandlertputArticleR::HasRepst⇒ArticleId→Handlert
See ref.[12]
See ref.[14] Authentication plugins:OpenID,BrowserID, Email, GoogleEmail, HashDB, RpxNow.[15]
See ref.[17] Session back-ends: ClientSession[18] (it stores the session in a cookie), ServerSession[19][20] (it stores most of the session data at the server)
A success, failure or indicative message can be stored (setMessage) in the Session and will be shown, if it exists, by thedefault_layout routine through thedefault_layout.hamlet template, being cleared on consultation.[21]
Common URL prefix subsites for workflows, file serving or site partitioning. See ref.[22][23]
Built-in subsites: Static,[24][25] Auth[26]
See ref.[27]
TheForm type here is an object that is used in thecontroller to parse and process the form fields user input and produce a (FormResult, Widget) pair were the widget holds the layout of the next rendering of the form with error messages and marks. It can also be used to generate a new form with blanks or default values.
The form type takes the shape of a function of an html snippet to be embedded in the view, that will hold security purpose hidden fields.
A form object is generated from anApplicative –Monadic composition of fields for a combined, sequential parsing of field inputs.
There are three types of forms:
The field generators, whose names are composed by the form type initial(a|m|i)followed by(req|opt){- required or optional -}, have a fieldParse component and a fieldView one.[28]
runForm{Post|Get} runs the field parsers against the form field inputs and generates a (FormResult, Widget) pair from the views offering a new form widget with the received form field values as defaults. The function suffix is the http method used in the form submission.generateForm{Post|Get} ignores inputs from the client and generates a blank or defaults form widget.[29]The actual function parameters and types have changed through Yesod versions. Check the Yesod book and libraries signatures.
The magic is in theFormResult data type Applicative instance, where(<*>) collects the error messages for the case ofFormFailure [textErrMsg] result values[30]
Monadic forms permit free form layout and better treatment ofhiddenField members.[27]
A sample of anApplicative[31] form:
-- a record for our form fieldsdataPerson=Person{personName::Text,personAge::Int,personLikings::MaybeText}-- the Form type has an extra parameter for an html snippet to be embedded, containing a CSRF token hidden field for securitytypeFormsubmasterx=Html→MFormsubmaster(FormResultx,Widget){--- for messages in validation functions: @param master: yesod instance to use in renderMessage (return from handler's getYesod) @param languages: page languages to use in renderMessage-- optional defaults record: @param mbPersonDefaults: Just defaults_record, or Nothing for blank form-}personForm::MyFoundationType→[Text]→MaybePerson→FormsubmasterPerson{- ''aopt'' (optional field AForm component) for "Maybe" fields, ''areq'' (required fld AForm comp.) will insert the "required" attribute-}personFormmasterlanguagesmbPersonDefaults=renderTable$Person<$>areqtextFieldfldSettingsNamembNameDefault<*>areqcustomPersonAgeFieldfldSettingsAgembAgeDefault<*>aopttextareaFieldfldSettingsLikingsmbLikingsDefaultwherembNameDefault=fmappersonNamembPersonDefaultsmbAgeDefault=fmappersonAgembPersonDefaultsmbLikingsDefault=fmappersonLikingsmbPersonDefaults-- "fieldSettingsLabel" returns an initial fieldSettings record-- recently the "FieldSettings" record can be defined from a String label since it implements IsStringfldSettingsName=(fieldSettingsLabelMsgName){fsAttrs=[("maxlength","20")]}fldSettingsAge=fieldSettingsLabelMsgAgefldSettingsLikings=(fieldSettingsLabelMsgLikings){fsAttrs=[("cols","40"),("rows","10")]}customPersonAgeField=checkvalidateAgeintFieldvalidateAgey|y<18=Left$renderMessagemasterlanguagesMsgUnderAge|otherwise=Righty
The types shown correspond to an older version, but the philosophy remains.
The Handler monad returns content in one or more of several formats as components of types that implement theHasReps class[32] {RepHtml, RepJson, RepXml, RepPlain, the dualRepHtmlJson, a pair or list of pairs[(ContentType, Content)], ..}.[33][34] Json examples:[35][36][37]
TheHasReps default implementation ofchooseRep chooses the document representation to be returned according to the preferred content-type list of the clientaccept header.[32]
A Widget monad,[39] based on a Writer[40] one and argument todefaultLayout, facilitate to piece the widgets together.
[qq| ... |] introduces an indentation based structured html template. (See doc.[42]).[43]'$' prefixes lines of logic statements.
Automatic closing tags are generated only for the tag at line start position.
toWidget [hamlet|$doctype 5<html><!-- only the tag at the beginning of the line will be automatically closed --><!-- '.' or '#' prefixes in tags introduce class/id names, à la CSS --><!-- ":boolVar:" prefix in attributes makes them conditionally generated --><!-- interpolation of haskell expressions follow the "shakespearean templates" syntax introduced in the so named section --><head><title>#{pageTitle} - My Site<linkrel=stylesheethref=@{Stylesheet_route}><body><header> ^{headerTemplate}<section#mySectionId><p><span.titleClass>_{MsgArticleListTitle}</span>$if null articles<p:isRed:style="color:red">_{MsgSorryNoArticles}$else<ul>$forall art<-articles<li>#{articleNumber art} .-#{articleTitle art}<footer> ^{footerTemplate}|]
See ref.[42]These are content view templates that follow a common substitution pattern of code expressions within curly brackets with different character prefix to refer to
^{...}^{template params},@{...}@{HomeR},_{...}_{MsgMessageLabel params}#{...}#{haskell_expression} which type must be convertible<isoLanguage>.msg" files) with parameter interpolations, the expression type must be an instance of Text.Shakespeare.I18N.ToMessage[47]Using non-English text in expressions requires use of theUnicode-aware typeText, since theGlasgow Haskell Compiler's (GHC's)show for the typeString renders non-ASCII characters as escaped numerical codes.
See ref.[57]
Yesod app messages are localizable (i18n). They should be held within themessages folder, in files named based onISO, as<iso-language>.msg
Message entries follow theEBNF pattern:
-- EBNF: identifier, {' ', parameter, '@', type}, ":", text with interpolationsArticleUnexistentparam@Int64:unexistentarticle#{param}
-- in codemyMsg::MyAppMessage-- datatype appending "Message" to the foundation typemyMsg=MsgArticleUnexistentmyArticleId-- constructor prepending "Msg" to the msg. label-- in widget templates_{MsgArticleUnexistentmyArticleId}
Actuali18n support is missing from thestack app template. ThemkMessage "MyApp" messagesFolder isoLangDefault must be added to the "Foundation.hs" file to get the messages instantiated.[58]
E.g. a visitor count. See ref.[62]
There is first class support forPostgreSQL,SQLite,MongoDB,CouchDB andMySQL, with experimental support forRedis.[63]
The Database layout is described in a template listing the entities, fields and constraints.[66]
share[mkPersistsqlSettings,mkMigrate"migrateAll"-- generates the migration procedure with the specified name][persist|User-- table name and entity record type-- implicit autoincrement column "id" as primary key, typed UserIdidentText-- refers to db. table column "ident";-- generates a record field prefixing the table name as "userIdent"passwordTextMaybe-- Maybe indicates Nullable fieldUniqueUserident-- unique constraint with space sep. field sequenceEmail-- table name and entity record type-- implicit autoincrement column "id" as primary key, typed EmailIdemailTextuserUserId-- foreign key by specifying other tables EntityField typesverkeyTextMaybenewlyAddedColumnText"default='sometext'::character varying"-- sql level Default constraintUniqueEmailemail-- unique constraint|]
Example forpersistent rawSQL andEsqueleto queries.[71]
The following packages are part of theyesod-platform:[72]
New Yesod apps are generated from the HaskellStack tool[76] templates, replacing previous command "yesod init"
Stack based app. template names are prefixed by yesod as "yesod-{minimal | postgres | sqlite | mysql | mongo | ...}"
yesod devel run from the project site, recompiles and restarts the project at every file tree modification.yesod add-handler adds a new handler and module to the project, adding animport clause for the handler in the "Application" module.Keter is a process as a service that handles deployment and restart of Yesodweb app servers, and, perweb app, database creation forPostgreSQL.
The console commandyesod keter packs the web app. as a keter bundle for uploading to a keter folder named "incoming".
Keter monitors the "incoming" folder and unpacks the app. to a temporary one, then assigns the web app a port to listen to, and starts it.
Initially it worked withNginx asreverse proxy (keter version 0.1*), addingvirtual server entries to its configuration and makingNginx reload it, but now Keter itself provides its ownreverse proxy functionality, removing Nginx dependency and acting as the main web server.[81]
Old documentation (Nginx based).[82][83]