Big web framework refactoring

db4
Slava Pestov 2008-06-02 15:00:03 -05:00
parent 81d417f265
commit 9bd38767ab
52 changed files with 638 additions and 477 deletions

View File

@ -1,9 +1,16 @@
! Copyright (C) 2008 Slava Pestov. ! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: accessors sequences kernel assocs combinators http.server USING: accessors sequences kernel assocs combinators
validators http hashtables namespaces fry continuations locals validators http hashtables namespaces fry continuations locals
boxes xml.entities html.elements html.components io arrays math boxes
html.templates.chloe io arrays math ; xml.entities
http.server
http.server.responses
furnace
html.elements
html.components
html.templates.chloe
html.templates.chloe.syntax ;
IN: furnace.actions IN: furnace.actions
SYMBOL: params SYMBOL: params
@ -92,9 +99,3 @@ TUPLE: page-action < action template ;
: <page-action> ( -- page ) : <page-action> ( -- page )
page-action new-action page-action new-action
dup '[ , template>> <chloe-content> ] >>display ; dup '[ , template>> <chloe-content> ] >>display ;
TUPLE: feed-action < action feed ;
: <feed-action> ( -- feed )
feed-action new-action
dup '[ , feed>> call <feed-content> ] >>display ;

View File

@ -2,6 +2,8 @@
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: accessors assocs namespaces kernel sequences sets USING: accessors assocs namespaces kernel sequences sets
http.server http.server
http.server.filters
http.server.dispatchers
furnace.sessions furnace.sessions
furnace.auth.providers ; furnace.auth.providers ;
IN: furnace.auth IN: furnace.auth

View File

@ -18,6 +18,10 @@ html.elements
urls urls
http http
http.server http.server
http.server.dispatchers
http.server.filters
http.server.responses
furnace
furnace.auth furnace.auth
furnace.auth.providers furnace.auth.providers
furnace.auth.providers.db furnace.auth.providers.db
@ -60,7 +64,7 @@ M: user-saver dispose
! ! ! Login ! ! ! Login
: successful-login ( user -- response ) : successful-login ( user -- response )
username>> set-uid "$login" end-flow ; username>> set-uid URL" $login" end-flow ;
: login-failed ( -- * ) : login-failed ( -- * )
"invalid username or password" validation-error "invalid username or password" validation-error
@ -68,7 +72,7 @@ M: user-saver dispose
: <login-action> ( -- action ) : <login-action> ( -- action )
<page-action> <page-action>
"$login/login" >>template { login "login" } >>template
[ [
{ {
@ -97,7 +101,7 @@ M: user-saver dispose
: <register-action> ( -- action ) : <register-action> ( -- action )
<page-action> <page-action>
"$login/register" >>template { login "register" } >>template
[ [
{ {
@ -138,7 +142,7 @@ M: user-saver dispose
tri tri
] >>init ] >>init
"$login/edit-profile" >>template { login "edit-profile" } >>template
[ [
uid "username" set-value uid "username" set-value
@ -173,7 +177,7 @@ M: user-saver dispose
drop drop
"$login" end-flow URL" $login" end-flow
] >>submit ; ] >>submit ;
! ! ! Password recovery ! ! ! Password recovery
@ -219,7 +223,7 @@ SYMBOL: lost-password-from
: <recover-action-1> ( -- action ) : <recover-action-1> ( -- action )
<page-action> <page-action>
"$login/recover-1" >>template { login "recover-1" } >>template
[ [
{ {
@ -240,7 +244,7 @@ SYMBOL: lost-password-from
: <recover-action-2> ( -- action ) : <recover-action-2> ( -- action )
<page-action> <page-action>
"$login/recover-2" >>template ; { login "recover-2" } >>template ;
: <recover-action-3> ( -- action ) : <recover-action-3> ( -- action )
<page-action> <page-action>
@ -251,7 +255,7 @@ SYMBOL: lost-password-from
} validate-params } validate-params
] >>init ] >>init
"$login/recover-3" >>template { login "recover-3" } >>template
[ [
{ {
@ -273,20 +277,20 @@ SYMBOL: lost-password-from
URL" $login/recover-4" <redirect> URL" $login/recover-4" <redirect>
] [ ] [
<400> <403>
] if* ] if*
] >>submit ; ] >>submit ;
: <recover-action-4> ( -- action ) : <recover-action-4> ( -- action )
<page-action> <page-action>
"$login/recover-4" >>template ; { login "recover-4" } >>template ;
! ! ! Logout ! ! ! Logout
: <logout-action> ( -- action ) : <logout-action> ( -- action )
<action> <action>
[ [
f set-uid f set-uid
"$login/login" end-flow URL" $login" end-flow
] >>submit ; ] >>submit ;
! ! ! Authentication logic ! ! ! Authentication logic
@ -320,7 +324,7 @@ M: login call-responder* ( path responder -- response )
: <login-boilerplate> ( responder -- responder' ) : <login-boilerplate> ( responder -- responder' )
<boilerplate> <boilerplate>
"$login/boilerplate" >>template ; { login "boilerplate" } >>template ;
: <login> ( responder -- auth ) : <login> ( responder -- auth )
login new-dispatcher login new-dispatcher

View File

@ -30,11 +30,11 @@
</t:form> </t:form>
<p> <p>
<t:if code="http.server.auth.login:login-failed?"> <t:if t:code="furnace.auth.login:allow-registration?">
<t:a t:href="register">Register</t:a> <t:a t:href="register">Register</t:a>
</t:if> </t:if>
| |
<t:if code="http.server.auth.login:allow-password-recovery?"> <t:if t:code="furnace.auth.login:allow-password-recovery?">
<t:a t:href="recover-password">Recover Password</t:a> <t:a t:href="recover-password">Recover Password</t:a>
</t:if> </t:if>
</p> </p>

View File

@ -1,7 +1,11 @@
! Copyright (c) 2008 Slava Pestov ! Copyright (c) 2008 Slava Pestov
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: accessors kernel namespaces http.server html.templates USING: accessors kernel namespaces
html.templates.chloe locals ; html.templates html.templates.chloe
locals
http.server
http.server.filters
furnace ;
IN: furnace.boilerplate IN: furnace.boilerplate
TUPLE: boilerplate < filter-responder template ; TUPLE: boilerplate < filter-responder template ;

View File

@ -1,7 +1,8 @@
! Copyright (C) 2008 Slava Pestov. ! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: db db.pools io.pools http.server furnace.sessions USING: kernel accessors continuations namespaces destructors
kernel accessors continuations namespaces destructors ; db db.pools io.pools http.server http.server.filters
furnace.sessions ;
IN: furnace.db IN: furnace.db
TUPLE: db-persistence < filter-responder pool ; TUPLE: db-persistence < filter-responder pool ;

View File

@ -2,8 +2,8 @@
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: accessors namespaces sequences arrays kernel USING: accessors namespaces sequences arrays kernel
assocs assocs.lib hashtables math.parser urls combinators assocs assocs.lib hashtables math.parser urls combinators
html.elements http http.server furnace.sessions furnace http http.server http.server.filters furnace.sessions
html.templates.chloe.syntax ; html.elements html.templates.chloe.syntax ;
IN: furnace.flows IN: furnace.flows
TUPLE: flows < filter-responder ; TUPLE: flows < filter-responder ;

View File

@ -0,0 +1,30 @@
IN: furnace.tests
USING: http.server.dispatchers http.server.responses
http.server furnace tools.test kernel namespaces accessors ;
TUPLE: funny-dispatcher < dispatcher ;
: <funny-dispatcher> funny-dispatcher new-dispatcher ;
TUPLE: base-path-check-responder ;
C: <base-path-check-responder> base-path-check-responder
M: base-path-check-responder call-responder*
2drop
"$funny-dispatcher" resolve-base-path
"text/plain" <content> ;
[ ] [
<dispatcher>
<dispatcher>
<funny-dispatcher>
<base-path-check-responder> "c" add-responder
"b" add-responder
"a" add-responder
main-responder set
] unit-test
[ "/a/b/" ] [
V{ } responder-nesting set
"a/b/c" split-path main-responder get call-responder body>>
] unit-test

View File

@ -1,7 +1,69 @@
! Copyright (C) 2003, 2008 Slava Pestov. ! Copyright (C) 2003, 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: accessors arrays kernel combinators assocs
continuations namespaces sequences splitting words
vocabs.loader classes
fry urls multiline
xml
xml.data
xml.writer
xml.utilities
html.components
html.elements
html.templates
html.templates.chloe
html.templates.chloe.syntax
http
http.server
http.server.redirection
http.server.responses
qualified ;
QUALIFIED-WITH: assocs a
IN: furnace IN: furnace
: nested-responders ( -- seq )
responder-nesting get a:values ;
: each-responder ( quot -- )
nested-responders swap each ; inline
: base-path ( string -- pair )
dup responder-nesting get
[ second class word-name = ] with find nip
[ first ] [ "No such responder: " swap append throw ] ?if ;
: resolve-base-path ( string -- string' )
"$" ?head [
[
"/" split1 [ base-path [ "/" % % ] each "/" % ] dip %
] "" make
] when ;
: vocab-path ( vocab -- path )
dup vocab-dir vocab-append-path ;
: resolve-template-path ( pair -- path )
[
first2 [ word-vocabulary vocab-path % ] [ "/" % % ] bi*
] "" make ;
GENERIC: modify-query ( query responder -- query' )
M: object modify-query drop ;
: adjust-url ( url -- url' )
clone
[ [ modify-query ] each-responder ] change-query
[ resolve-base-path ] change-path
relative-to-request ;
: <redirect> ( url -- response )
adjust-url request get method>> {
{ "GET" [ <temporary-redirect> ] }
{ "HEAD" [ <temporary-redirect> ] }
{ "POST" [ <permanent-redirect> ] }
} case ;
GENERIC: hidden-form-field ( responder -- ) GENERIC: hidden-form-field ( responder -- )
M: object hidden-form-field drop ; M: object hidden-form-field drop ;
@ -13,12 +75,6 @@ M: object hidden-form-field drop ;
{ "POST" [ post-data>> ] } { "POST" [ post-data>> ] }
} case ; } case ;
: <feed-content> ( body -- response )
feed>xml "application/atom+xml" <content> ;
: <json-content> ( obj -- response )
>json "application/json" <content> ;
SYMBOL: exit-continuation SYMBOL: exit-continuation
: exit-with exit-continuation get continue-with ; : exit-with exit-continuation get continue-with ;
@ -38,7 +94,7 @@ CHLOE: atom
<url> <url>
swap >>query swap >>query
swap >>path swap >>path
adjust-url adjust-url relative-to-request
add-atom-feed ; add-atom-feed ;
CHLOE: write-atom drop write-atom-feeds ; CHLOE: write-atom drop write-atom-feeds ;
@ -62,7 +118,7 @@ M: object link-attr 2drop ;
<url> <url>
swap >>query swap >>query
swap >>path swap >>path
adjust-url =href adjust-url relative-to-request =href
a> a>
] with-scope ; ] with-scope ;
@ -94,8 +150,6 @@ CHLOE: form
[ drop </form> ] [ drop </form> ]
tri ; tri ;
DEFER: process-chloe-tag
STRING: button-tag-markup STRING: button-tag-markup
<t:form class="inline" xmlns:t="http://factorcode.org/chloe/1.0"> <t:form class="inline" xmlns:t="http://factorcode.org/chloe/1.0">
<button type="submit"></button> <button type="submit"></button>
@ -124,13 +178,6 @@ CHLOE: button
] unless ; ] unless ;
: if-satisfied? ( tag -- ? ) : if-satisfied? ( tag -- ? )
t swap "code" required-attr attr>word execute ;
{
[ "code" optional-attr [ attr>word execute and ] when* ]
[ "var" optional-attr [ attr>var get and ] when* ]
[ "svar" optional-attr [ attr>var sget and ] when* ]
[ "uvar" optional-attr [ attr>var uget and ] when* ]
[ "value" optional-attr [ value and ] when* ]
} cleave ;
CHLOE: if dup if-satisfied? [ process-tag-children ] [ drop ] if ; CHLOE: if dup if-satisfied? [ process-tag-children ] [ drop ] if ;

View File

@ -0,0 +1,7 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: json.writer http.server.responses ;
IN: furnace.json
: <json-content> ( body -- response )
>json "application/json" <content> ;

View File

@ -0,0 +1,14 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: accessors kernel fry
rss http.server.responses furnace.actions ;
IN: furnace.rss
: <feed-content> ( body -- response )
feed>xml "application/atom+xml" <content> ;
TUPLE: feed-action < action feed ;
: <feed-action> ( -- feed )
feed-action new-action
dup '[ , feed>> call <feed-content> ] >>display ;

View File

@ -1,8 +1,10 @@
IN: furnace.sessions.tests IN: furnace.sessions.tests
USING: tools.test http furnace.sessions USING: tools.test http furnace.sessions
furnace.actions http.server math namespaces kernel accessors furnace.actions http.server http.server.responses
math namespaces kernel accessors
prettyprint io.streams.string io.files splitting destructors prettyprint io.streams.string io.files splitting destructors
sequences db db.sqlite continuations urls ; sequences db db.sqlite continuations urls math.parser
furnace ;
: with-session : with-session
[ [

View File

@ -4,7 +4,8 @@ USING: assocs kernel math.intervals math.parser namespaces
random accessors quotations hashtables sequences continuations random accessors quotations hashtables sequences continuations
fry calendar combinators destructors alarms fry calendar combinators destructors alarms
db db.tuples db.types db db.tuples db.types
http http.server html.elements html.templates.chloe ; http http.server http.server.dispatchers http.server.filters
html.elements furnace ;
IN: furnace.sessions IN: furnace.sessions
TUPLE: session id expires uid namespace changed? ; TUPLE: session id expires uid namespace changed? ;
@ -151,11 +152,3 @@ M: sessions call-responder* ( path responder -- response )
: logout-all-sessions ( uid -- ) : logout-all-sessions ( uid -- )
session new swap >>uid delete-tuples ; session new swap >>uid delete-tuples ;
M: sessions link-attr
drop
"session" optional-attr {
{ "none" [ session off flow-id off ] }
{ "current" [ ] }
{ f [ ] }
} case ;

View File

@ -190,7 +190,7 @@ SYMBOL: html
<html "http://www.w3.org/1999/xhtml" =xmlns "en" =xml:lang "en" =lang html> <html "http://www.w3.org/1999/xhtml" =xmlns "en" =xml:lang "en" =lang html>
<head> <title> swap write </title> </head> <head> <title> swap write </title> </head>
<body> call </body> <body> call </body>
</html> ; </html> ; inline
: render-error ( message -- ) : render-error ( message -- )
<span "error" =class span> escape-string write </span> ; <span "error" =class span> escape-string write </span> ;

View File

@ -49,7 +49,7 @@ IN: html.templates.chloe.tests
[ [
[ [
"test2" test-template call-template "test2" test-template call-template
] "test3" test-template with-boilerplate ] [ "test3" test-template ] with-boilerplate
] run-template ] run-template
] unit-test ] unit-test
@ -69,24 +69,6 @@ IN: html.templates.chloe.tests
] run-template ] run-template
] unit-test ] unit-test
SYMBOL: test6-aux?
[ "True" ] [
[
test6-aux? on
"test6" test-template call-template
] run-template
] unit-test
SYMBOL: test7-aux?
[ "" ] [
[
test7-aux? off
"test7" test-template call-template
] run-template
] unit-test
[ ] [ blank-values ] unit-test [ ] [ blank-values ] unit-test
[ ] [ "A label" "label" set-value ] unit-test [ ] [ "A label" "label" set-value ] unit-test
@ -127,7 +109,7 @@ M: link-test link-href drop "http://www.apple.com/foo&bar" ;
[ "<ul><li>1</li><li>2</li><li>3</li></ul>" ] [ [ "<ul><li>1</li><li>2</li><li>3</li></ul>" ] [
[ [
"test9" test-template call-template "test7" test-template call-template
] run-template [ blank? not ] filter ] run-template [ blank? not ] filter
] unit-test ] unit-test
@ -142,7 +124,7 @@ TUPLE: person first-name last-name ;
[ "<table><tr><td>RBaxter</td><td>Unknown</td></tr><tr><td>Doug</td><td>Coleman</td></tr></table>" ] [ [ "<table><tr><td>RBaxter</td><td>Unknown</td></tr><tr><td>Doug</td><td>Coleman</td></tr></table>" ] [
[ [
"test10" test-template call-template "test8" test-template call-template
] run-template [ blank? not ] filter ] run-template [ blank? not ] filter
] unit-test ] unit-test
@ -155,7 +137,7 @@ TUPLE: person first-name last-name ;
[ "<table><tr><td>RBaxter</td><td>Unknown</td></tr><tr><td>Doug</td><td>Coleman</td></tr></table>" ] [ [ "<table><tr><td>RBaxter</td><td>Unknown</td></tr><tr><td>Doug</td><td>Coleman</td></tr></table>" ] [
[ [
"test10" test-template call-template "test9" test-template call-template
] run-template [ blank? not ] filter ] run-template [ blank? not ] filter
] unit-test ] unit-test
@ -163,6 +145,6 @@ TUPLE: person first-name last-name ;
[ "<a name=\"1\">Hello</a>" ] [ [ "<a name=\"1\">Hello</a>" ] [
[ [
"test11" test-template call-template "test10" test-template call-template
] run-template ] run-template
] unit-test ] unit-test

View File

@ -12,6 +12,7 @@ html.templates.chloe.syntax ;
IN: html.templates.chloe IN: html.templates.chloe
! Chloe is Ed's favorite web designer ! Chloe is Ed's favorite web designer
SYMBOL: tag-stack
TUPLE: chloe path ; TUPLE: chloe path ;
@ -44,7 +45,8 @@ CHLOE: title children>string set-title ;
CHLOE: write-title CHLOE: write-title
drop drop
"head" tags get member? "title" tags get member? not and "head" tag-stack get member?
"title" tag-stack get member? not and
[ <title> write-title </title> ] [ write-title ] if ; [ <title> write-title </title> ] [ write-title ] if ;
CHLOE: style CHLOE: style
@ -92,22 +94,23 @@ CHLOE-SINGLETON: html
CHLOE-SINGLETON: hidden CHLOE-SINGLETON: hidden
CHLOE-TUPLE: field CHLOE-TUPLE: field
CHLOE-TUPLE: textarea
CHLOE-TUPLE: password CHLOE-TUPLE: password
CHLOE-TUPLE: choice CHLOE-TUPLE: choice
CHLOE-TUPLE: checkbox CHLOE-TUPLE: checkbox
CHLOE-TUPLE: code CHLOE-TUPLE: code
: process-chloe-tag ( tag -- ) : process-chloe-tag ( tag -- )
dup name-tag tags get at dup name-tag dup tags get at
[ call ] [ "Unknown chloe tag: " prepend throw ] ?if ; [ call ] [ "Unknown chloe tag: " prepend throw ] ?if ;
: process-tag ( tag -- ) : process-tag ( tag -- )
{ {
[ name-tag >lower tags get push ] [ name-tag >lower tag-stack get push ]
[ write-start-tag ] [ write-start-tag ]
[ process-tag-children ] [ process-tag-children ]
[ write-end-tag ] [ write-end-tag ]
[ drop tags get pop* ] [ drop tag-stack get pop* ]
} cleave ; } cleave ;
: expand-attrs ( tag -- tag ) : expand-attrs ( tag -- tag )
@ -127,7 +130,7 @@ CHLOE-TUPLE: code
: process-chloe ( xml -- ) : process-chloe ( xml -- )
[ [
V{ } clone tags set V{ } clone tag-stack set
nested-template? get [ nested-template? get [
process-template process-template

View File

@ -14,11 +14,10 @@ SYMBOL: tags
tags global [ H{ } clone or ] change-at tags global [ H{ } clone or ] change-at
: define-chloe-tag ( name quot -- ) tags get set-at ; : define-chloe-tag ( name quot -- ) swap tags get set-at ;
: CHLOE: : CHLOE:
scan parse-definition swap define-chloe-tag ; scan parse-definition define-chloe-tag ; parsing
parsing
: chloe-ns "http://factorcode.org/chloe/1.0" ; inline : chloe-ns "http://factorcode.org/chloe/1.0" ; inline
@ -38,7 +37,9 @@ MEMO: chloe-name ( string -- name )
[ "name" required-attr ] dip render ; [ "name" required-attr ] dip render ;
: CHLOE-SINGLETON: : CHLOE-SINGLETON:
scan dup '[ , singleton-component-tag ] define-chloe-tag ; scan-word
[ word-name ] [ '[ , singleton-component-tag ] ] bi
define-chloe-tag ;
parsing parsing
: attrs>slots ( tag tuple -- ) : attrs>slots ( tag tuple -- )
@ -54,5 +55,7 @@ MEMO: chloe-name ( string -- name )
2bi render ; 2bi render ;
: CHLOE-TUPLE: : CHLOE-TUPLE:
scan dup '[ , tuple-component-tag ] define-chloe-tag ; scan-word
[ word-name ] [ '[ , tuple-component-tag ] ] bi
define-chloe-tag ;
parsing parsing

View File

@ -2,8 +2,26 @@
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0"> <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<t:if t:var="html.templates.chloe.tests:test6-aux?"> <t:label t:name="label" />
True
</t:if> <t:link t:name="link" />
<t:code t:name="code" mode="mode" />
<t:farkup t:name="farkup" />
<t:inspector t:name="inspector" />
<t:html t:name="html" />
<t:field t:name="field" t:size="13" />
<t:password t:name="password" t:size="10" />
<t:textarea t:name="textarea" t:rows="5" t:cols="10" />
<t:choice t:name="choice" t:choices="choices" />
<t:checkbox t:name="checkbox">Checkbox</t:checkbox>
</t:chloe> </t:chloe>

View File

@ -2,8 +2,10 @@
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0"> <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<t:if t:var="html.templates.chloe.tests:test7-aux?"> <ul>
True <t:each t:name="numbers">
</t:if> <li><t:label t:name="value"/></li>
</t:each>
</ul>
</t:chloe> </t:chloe>

View File

@ -2,26 +2,13 @@
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0"> <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<t:label t:name="label" /> <table>
<t:bind-each t:name="people">
<t:link t:name="link" /> <tr>
<td><t:label t:name="first-name"/></td>
<t:code t:name="code" mode="mode" /> <td><t:label t:name="last-name"/></td>
</tr>
<t:farkup t:name="farkup" /> </t:bind-each>
</table>
<t:inspector t:name="inspector" />
<t:html t:name="html" />
<t:field t:name="field" t:size="13" />
<t:password t:name="password" t:size="10" />
<t:textarea t:name="textarea" t:rows="5" t:cols="10" />
<t:choice t:name="choice" t:choices="choices" />
<t:checkbox t:name="checkbox">Checkbox</t:checkbox>
</t:chloe> </t:chloe>

View File

@ -1,11 +1,3 @@
<?xml version='1.0' ?> <?xml version='1.0' ?>
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0"> <t:chloe xmlns:t="http://factorcode.org/chloe/1.0"><a name="@id">Hello</a></t:chloe>
<ul>
<t:each t:name="numbers">
<li><t:label t:name="value"/></li>
</t:each>
</ul>
</t:chloe>

View File

@ -10,30 +10,26 @@ tuple-syntax namespaces urls ;
[ [
TUPLE{ request TUPLE{ request
url: TUPLE{ url protocol: "http" host: "www.apple.com" port: 80 path: "/index.html" query: H{ } } url: TUPLE{ url protocol: "http" host: "www.apple.com" port: 80 path: "/index.html" }
method: "GET" method: "GET"
version: "1.1" version: "1.1"
cookies: V{ } cookies: V{ }
header: H{ { "connection" "close" } { "user-agent" "Factor http.client vocabulary" } } header: H{ { "connection" "close" } { "user-agent" "Factor http.client vocabulary" } }
} }
] [ ] [
[ "http://www.apple.com/index.html"
"http://www.apple.com/index.html" <get-request>
<get-request>
] with-scope
] unit-test ] unit-test
[ [
TUPLE{ request TUPLE{ request
url: TUPLE{ url protocol: "https" host: "www.amazon.com" port: 443 path: "/index.html" query: H{ } } url: TUPLE{ url protocol: "https" host: "www.amazon.com" port: 443 path: "/index.html" }
method: "GET" method: "GET"
version: "1.1" version: "1.1"
cookies: V{ } cookies: V{ }
header: H{ { "connection" "close" } { "user-agent" "Factor http.client vocabulary" } } header: H{ { "connection" "close" } { "user-agent" "Factor http.client vocabulary" } }
} }
] [ ] [
[ "https://www.amazon.com/index.html"
"https://www.amazon.com/index.html" <get-request>
<get-request>
] with-scope
] unit-test ] unit-test

View File

@ -4,7 +4,7 @@ USING: assocs http kernel math math.parser namespaces sequences
io io.sockets io.streams.string io.files io.timeouts strings io io.sockets io.streams.string io.files io.timeouts strings
splitting calendar continuations accessors vectors math.order splitting calendar continuations accessors vectors math.order
io.encodings.8-bit io.encodings.binary io.streams.duplex io.encodings.8-bit io.encodings.binary io.streams.duplex
fry debugger inspector ascii ; fry debugger inspector ascii urls ;
IN: http.client IN: http.client
: max-redirects 10 ; : max-redirects 10 ;
@ -21,13 +21,16 @@ DEFER: http-request
SYMBOL: redirects SYMBOL: redirects
: redirect-url ( request url -- request )
'[ , >url derive-url ensure-port ] change-url ;
: do-redirect ( response data -- response data ) : do-redirect ( response data -- response data )
over code>> 300 399 between? [ over code>> 300 399 between? [
drop drop
redirects inc redirects inc
redirects get max-redirects < [ redirects get max-redirects < [
request get request get
swap "location" header request-with-url swap "location" header redirect-url
"GET" >>method http-request "GET" >>method http-request
] [ ] [
too-many-redirects too-many-redirects
@ -61,8 +64,8 @@ PRIVATE>
: <get-request> ( url -- request ) : <get-request> ( url -- request )
<request> <request>
swap request-with-url "GET" >>method
"GET" >>method ; swap >url ensure-port >>url ;
: http-get* ( url -- response data ) : http-get* ( url -- response data )
<get-request> http-request ; <get-request> http-request ;
@ -100,7 +103,7 @@ M: download-failed error.
: <post-request> ( content-type content url -- request ) : <post-request> ( content-type content url -- request )
<request> <request>
"POST" >>method "POST" >>method
swap request-with-url swap >url ensure-port >>url
swap >>post-data swap >>post-data
swap >>post-data-type ; swap >>post-data-type ;

View File

@ -3,11 +3,6 @@ io.streams.string kernel arrays splitting sequences
assocs io.sockets db db.sqlite continuations urls ; assocs io.sockets db db.sqlite continuations urls ;
IN: http.tests IN: http.tests
[ "/" ] [ "http://foo.com" url>path ] unit-test
[ "/" ] [ "http://foo.com/" url>path ] unit-test
[ "/bar" ] [ "http://foo.com/bar" url>path ] unit-test
[ "/bar" ] [ "/bar" url>path ] unit-test
: lf>crlf "\n" split "\r\n" join ; : lf>crlf "\n" split "\r\n" join ;
STRING: read-request-test-1 STRING: read-request-test-1
@ -126,7 +121,9 @@ read-response-test-1' 1array [
USING: http.server http.server.static furnace.sessions USING: http.server http.server.static furnace.sessions
furnace.actions furnace.auth.login furnace.db http.client furnace.actions furnace.auth.login furnace.db http.client
io.server io.files io io.encodings.ascii io.server io.files io io.encodings.ascii
accessors namespaces threads ; accessors namespaces threads
http.server.responses http.server.redirection
http.server.dispatchers ;
: add-quit-action : add-quit-action
<action> <action>
@ -149,7 +146,7 @@ test-db [
"resource:extra/http/test" <static> >>default "resource:extra/http/test" <static> >>default
"nested" add-responder "nested" add-responder
<action> <action>
[ URL" redirect-loop" <redirect> ] >>display [ URL" redirect-loop" <temporary-redirect> ] >>display
"redirect-loop" add-responder "redirect-loop" add-responder
main-responder set main-responder set

View File

@ -6,8 +6,7 @@ assocs sequences splitting sorting sets debugger
strings vectors hashtables quotations arrays byte-arrays strings vectors hashtables quotations arrays byte-arrays
math.parser calendar calendar.format math.parser calendar calendar.format
io io.streams.string io.encodings.utf8 io.encodings.string io io.server io.sockets.secure
io.sockets io.sockets.secure io.server
unicode.case unicode.categories qualified unicode.case unicode.categories qualified
@ -17,22 +16,6 @@ EXCLUDE: fry => , ;
IN: http IN: http
: secure-protocol? ( protocol -- ? )
"https" = ;
: url-addr ( url -- addr )
[ [ host>> ] [ port>> ] bi <inet> ] [ protocol>> ] bi
secure-protocol? [ <secure> ] when ;
: protocol-port ( protocol -- port )
{
{ "http" [ 80 ] }
{ "https" [ 443 ] }
} case ;
: ensure-port ( url -- url' )
dup protocol>> '[ , protocol-port or ] change-port ;
: crlf "\r\n" write ; : crlf "\r\n" write ;
: add-header ( value key assoc -- ) : add-header ( value key assoc -- )
@ -167,19 +150,6 @@ cookies ;
"close" "connection" set-header "close" "connection" set-header
"Factor http.client vocabulary" "user-agent" set-header ; "Factor http.client vocabulary" "user-agent" set-header ;
: chop-hostname ( str -- str' )
":" split1 "//" ?head drop nip
CHAR: / over index over length or tail
dup empty? [ drop "/" ] when ;
: url>path ( url -- path )
#! Technically, only proxies are meant to support hostnames
#! in HTTP requests, but IE sends these sometimes so we
#! just chop the hostname part.
url-decode
dup { "http://" "https://" } [ head? ] with contains?
[ chop-hostname ] when ;
: read-method ( request -- request ) : read-method ( request -- request )
" " read-until [ "Bad request: method" throw ] unless " " read-until [ "Bad request: method" throw ] unless
>>method ; >>method ;
@ -299,9 +269,6 @@ SYMBOL: max-post-request
flush flush
drop ; drop ;
: request-with-url ( request url -- request )
'[ , >url derive-url ensure-port ] change-url ;
GENERIC: write-response ( response -- ) GENERIC: write-response ( response -- )
GENERIC: write-full-response ( request response -- ) GENERIC: write-full-response ( request response -- )
@ -406,7 +373,7 @@ body ;
: <raw-response> ( -- response ) : <raw-response> ( -- response )
raw-response new raw-response new
"1.1" >>version ; "1.1" >>version ;
M: raw-response write-response ( respose -- ) M: raw-response write-response ( respose -- )
write-response-version write-response-version

View File

@ -0,0 +1,97 @@
USING: http.server http.server.dispatchers http.server.responses
tools.test kernel namespaces accessors io http math sequences
assocs arrays classes words urls ;
IN: http.server.dispatchers.tests
\ find-responder must-infer
\ http-error. must-infer
TUPLE: mock-responder path ;
C: <mock-responder> mock-responder
M: mock-responder call-responder*
nip
path>> on
[ ] "text/plain" <content> ;
: check-dispatch ( tag path -- ? )
V{ } clone responder-nesting set
over off
split-path
main-responder get call-responder
write-response get ;
[
<dispatcher>
"foo" <mock-responder> "foo" add-responder
"bar" <mock-responder> "bar" add-responder
<dispatcher>
"123" <mock-responder> "123" add-responder
"default" <mock-responder> >>default
"baz" add-responder
main-responder set
[ "foo" ] [
{ "foo" } main-responder get find-responder path>> nip
] unit-test
[ "bar" ] [
{ "bar" } main-responder get find-responder path>> nip
] unit-test
[ t ] [ "foo" "foo" check-dispatch ] unit-test
[ f ] [ "foo" "bar" check-dispatch ] unit-test
[ t ] [ "bar" "bar" check-dispatch ] unit-test
[ t ] [ "default" "baz/xxx" check-dispatch ] unit-test
[ t ] [ "default" "baz/xxx//" check-dispatch ] unit-test
[ t ] [ "default" "/baz/xxx//" check-dispatch ] unit-test
[ t ] [ "123" "baz/123" check-dispatch ] unit-test
[ t ] [ "123" "baz///123" check-dispatch ] unit-test
] with-scope
[
<dispatcher>
"default" <mock-responder> >>default
main-responder set
[ "/default" ] [ "/default" main-responder get find-responder drop ] unit-test
] with-scope
! Make sure path for default responder isn't chopped
TUPLE: path-check-responder ;
C: <path-check-responder> path-check-responder
M: path-check-responder call-responder*
drop
>array "text/plain" <content> ;
[ { "c" } ] [
V{ } clone responder-nesting set
{ "b" "c" }
<dispatcher>
<dispatcher>
<path-check-responder> >>default
"b" add-responder
call-responder
body>>
] unit-test
! Test that "" dispatcher works with default>>
[ ] [
<dispatcher>
"" <mock-responder> "" add-responder
"bar" <mock-responder> "bar" add-responder
"baz" <mock-responder> >>default
main-responder set
[ t ] [ "" "" check-dispatch ] unit-test
[ f ] [ "" "quux" check-dispatch ] unit-test
[ t ] [ "baz" "quux" check-dispatch ] unit-test
[ f ] [ "foo" "bar" check-dispatch ] unit-test
[ t ] [ "bar" "bar" check-dispatch ] unit-test
[ t ] [ "baz" "xxx" check-dispatch ] unit-test
] unit-test

View File

@ -0,0 +1,47 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: kernel namespaces sequences assocs accessors
http http.server http.server.responses ;
IN: http.server.dispatchers
TUPLE: dispatcher default responders ;
: new-dispatcher ( class -- dispatcher )
new
<404> <trivial-responder> >>default
H{ } clone >>responders ; inline
: <dispatcher> ( -- dispatcher )
dispatcher new-dispatcher ;
: find-responder ( path dispatcher -- path responder )
over empty? [
"" over responders>> at*
[ nip ] [ drop default>> ] if
] [
over first over responders>> at*
[ [ drop rest-slice ] dip ] [ drop default>> ] if
] if ;
M: dispatcher call-responder* ( path dispatcher -- response )
find-responder call-responder ;
TUPLE: vhost-dispatcher default responders ;
: <vhost-dispatcher> ( -- dispatcher )
vhost-dispatcher new-dispatcher ;
: find-vhost ( dispatcher -- responder )
request get url>> host>> over responders>> at*
[ nip ] [ drop default>> ] if ;
M: vhost-dispatcher call-responder* ( path dispatcher -- response )
find-vhost call-responder ;
: add-responder ( dispatcher responder path -- dispatcher )
pick responders>> set-at ;
: add-main-responder ( dispatcher responder path -- dispatcher )
[ add-responder drop ]
[ drop "" add-responder drop ]
[ 2drop ] 3tri ;

View File

@ -0,0 +1,9 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: http.server accessors ;
IN: http.server.filters
TUPLE: filter-responder responder ;
M: filter-responder call-responder*
responder>> call-responder ;

View File

@ -0,0 +1,48 @@
IN: http.server.redirection.tests
USING: http http.server.redirection urls accessors
namespaces tools.test ;
\ relative-to-request must-infer
[
<request>
<url>
"http" >>protocol
"www.apple.com" >>host
"/xxx/bar" >>path
{ { "a" "b" } } >>query
>>url
request set
[ "http://www.apple.com:80/xxx/bar" ] [
<url> relative-to-request url>string
] unit-test
[ "http://www.apple.com:80/xxx/baz" ] [
<url> "baz" >>path relative-to-request url>string
] unit-test
[ "http://www.apple.com:80/xxx/baz?c=d" ] [
<url> "baz" >>path { { "c" "d" } } >>query relative-to-request url>string
] unit-test
[ "http://www.apple.com:80/xxx/bar?c=d" ] [
<url> { { "c" "d" } } >>query relative-to-request url>string
] unit-test
[ "http://www.apple.com:80/flip" ] [
<url> "/flip" >>path relative-to-request url>string
] unit-test
[ "http://www.apple.com:80/flip?c=d" ] [
<url> "/flip" >>path { { "c" "d" } } >>query relative-to-request url>string
] unit-test
[ "http://www.jedit.org:80/" ] [
"http://www.jedit.org" >url relative-to-request url>string
] unit-test
[ "http://www.jedit.org:80/?a=b" ] [
"http://www.jedit.org" >url { { "a" "b" } } >>query relative-to-request url>string
] unit-test
] with-scope

View File

@ -0,0 +1,24 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: kernel accessors combinators namespaces
logging urls http http.server http.server.responses ;
IN: http.server.redirection
: relative-to-request ( url -- url' )
request get url>>
clone
f >>query
swap derive-url ensure-port ;
: <custom-redirect> ( url code message -- response )
<trivial-response>
swap dup url? [ relative-to-request ] when
"location" set-header ;
\ <custom-redirect> DEBUG add-input-logging
: <permanent-redirect> ( url -- response )
301 "Moved Permanently" <custom-redirect> ;
: <temporary-redirect> ( url -- response )
307 "Temporary Redirect" <custom-redirect> ;

View File

@ -0,0 +1,37 @@
! Copyright (C) 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license.
USING: html.elements math.parser http accessors kernel
io io.streams.string ;
IN: http.server.responses
: <content> ( body content-type -- response )
<response>
200 >>code
"Document follows" >>message
swap >>content-type
swap >>body ;
: trivial-response-body ( code message -- )
<html>
<body>
<h1> [ number>string write bl ] [ write ] bi* </h1>
</body>
</html> ;
: <trivial-response> ( code message -- response )
2dup [ trivial-response-body ] with-string-writer
"text/html" <content>
swap >>message
swap >>code ;
: <304> ( -- response )
304 "Not modified" <trivial-response> ;
: <403> ( -- response )
403 "Forbidden" <trivial-response> ;
: <400> ( -- response )
400 "Bad request" <trivial-response> ;
: <404> ( -- response )
404 "Not found" <trivial-response> ;

View File

@ -1,217 +1,51 @@
! Copyright (C) 2003, 2008 Slava Pestov. ! Copyright (C) 2003, 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: assocs kernel namespaces io io.timeouts strings splitting USING: kernel accessors sequences arrays namespaces splitting
threads sequences prettyprint io.server logging calendar http vocabs.loader http http.server.responses logging calendar
html.streams html.components html.elements html.templates destructors html.elements html.streams io.server
accessors math.parser combinators.lib tools.vocabs debugger io.encodings.8-bit io.timeouts io assocs debugger continuations
continuations random combinators destructors io.streams.string fry tools.vocabs math ;
io.encodings.8-bit fry classes words math urls
arrays vocabs.loader ;
IN: http.server IN: http.server
SYMBOL: responder-nesting
SYMBOL: main-responder
SYMBOL: development-mode
! path is a sequence of path component strings ! path is a sequence of path component strings
GENERIC: call-responder* ( path responder -- response ) GENERIC: call-responder* ( path responder -- response )
: <content> ( body content-type -- response )
<response>
200 >>code
"Document follows" >>message
swap >>content-type
swap >>body ;
TUPLE: trivial-responder response ; TUPLE: trivial-responder response ;
C: <trivial-responder> trivial-responder C: <trivial-responder> trivial-responder
M: trivial-responder call-responder* nip response>> call ; M: trivial-responder call-responder* nip response>> clone ;
: trivial-response-body ( code message -- ) main-responder global [ <404> <trivial-responder> get-global or ] change-at
<html>
<body>
<h1> [ number>string write bl ] [ write ] bi* </h1>
</body>
</html> ;
: <trivial-response> ( code message -- response )
2dup [ trivial-response-body ] with-string-writer
"text/html" <content>
swap >>message
swap >>code ;
: <400> ( -- response )
400 "Bad request" <trivial-response> ;
: <404> ( -- response )
404 "Not Found" <trivial-response> ;
SYMBOL: 404-responder
[ <404> ] <trivial-responder> 404-responder set-global
SYMBOL: responder-nesting
: invert-slice ( slice -- slice' ) : invert-slice ( slice -- slice' )
dup slice? [ dup slice? [ [ seq>> ] [ from>> ] bi head-slice ] [ drop { } ] if ;
[ seq>> ] [ from>> ] bi head-slice
] [
drop { }
] if ;
: vocab-path ( vocab -- path ) : add-responder-nesting ( path responder -- )
dup vocab-dir vocab-append-path ; [ invert-slice ] dip 2array responder-nesting get push ;
: vocab-path-of ( dispatcher -- path )
class word-vocabulary vocab-path ;
: add-responder-path ( path dispatcher -- )
[ [ invert-slice ] [ [ vocab-path-of ] keep ] bi* 3array ]
[ nip class word-name ] 2bi
responder-nesting get set-at ;
: call-responder ( path responder -- response ) : call-responder ( path responder -- response )
[ add-responder-path ] [ call-responder* ] 2bi ; [ add-responder-nesting ] [ call-responder* ] 2bi ;
: nested-responders ( -- seq )
responder-nesting get assocs:values [ third ] map ;
: each-responder ( quot -- )
nested-responders swap each ; inline
: responder-path ( string -- pair )
dup responder-nesting get at
[ ] [ "No such responder: " swap append throw ] ?if ;
: base-path ( string -- path )
responder-path first ;
: template-path ( string -- path )
responder-path second ;
: resolve-responder-path ( string quot -- string' )
[ "$" ?head ] dip '[
[
"/" split1 [ @ [ "/" % % ] each "/" % ] dip %
] "" make
] when ; inline
: resolve-base-path ( string -- string' )
[ base-path ] resolve-responder-path ;
: resolve-template-path ( string -- string' )
[ template-path ] resolve-responder-path ;
GENERIC: modify-query ( query responder -- query' )
M: object modify-query drop ;
: adjust-url ( url -- url' )
clone
[ dup [ modify-query ] each-responder ] change-query
[ resolve-base-path ] change-path
request get url>>
clone
f >>query
swap derive-url ensure-port ;
: <custom-redirect> ( url code message -- response )
<trivial-response>
swap dup url? [ adjust-url ] when
"location" set-header ;
\ <custom-redirect> DEBUG add-input-logging
: <permanent-redirect> ( to query -- response )
301 "Moved Permanently" <custom-redirect> ;
: <temporary-redirect> ( to query -- response )
307 "Temporary Redirect" <custom-redirect> ;
: <redirect> ( to query -- response )
request get method>> {
{ "GET" [ <temporary-redirect> ] }
{ "HEAD" [ <temporary-redirect> ] }
{ "POST" [ <permanent-redirect> ] }
} case ;
TUPLE: dispatcher default responders ;
: new-dispatcher ( class -- dispatcher )
new
404-responder get >>default
H{ } clone >>responders ; inline
: <dispatcher> ( -- dispatcher )
dispatcher new-dispatcher ;
: find-responder ( path dispatcher -- path responder )
over empty? [
"" over responders>> at*
[ nip ] [ drop default>> ] if
] [
over first over responders>> at*
[ [ drop rest-slice ] dip ] [ drop default>> ] if
] if ;
M: dispatcher call-responder* ( path dispatcher -- response )
find-responder call-responder ;
TUPLE: vhost-dispatcher default responders ;
: <vhost-dispatcher> ( -- dispatcher )
404-responder get H{ } clone vhost-dispatcher boa ;
: find-vhost ( dispatcher -- responder )
request get url>> host>> over responders>> at*
[ nip ] [ drop default>> ] if ;
M: vhost-dispatcher call-responder* ( path dispatcher -- response )
find-vhost call-responder ;
: add-responder ( dispatcher responder path -- dispatcher )
pick responders>> set-at ;
: add-main-responder ( dispatcher responder path -- dispatcher )
[ add-responder drop ]
[ drop "" add-responder drop ]
[ 2drop ] 3tri ;
TUPLE: filter-responder responder ;
M: filter-responder call-responder*
responder>> call-responder ;
SYMBOL: main-responder
main-responder global
[ drop 404-responder get-global ] cache
drop
SYMBOL: development-mode
: http-error. ( error -- ) : http-error. ( error -- )
"Internal server error" [ "Internal server error" [
development-mode get [ [ print-error nl :c ] with-html-stream
[ print-error nl :c ] with-html-stream
] [
500 "Internal server error"
trivial-response-body
] if
] simple-page ; ] simple-page ;
: <500> ( error -- response ) : <500> ( error -- response )
500 "Internal server error" <trivial-response> 500 "Internal server error" <trivial-response>
swap '[ , http-error. ] >>body ; development-mode get [ swap '[ , http-error. ] >>body ] [ drop ] if ;
: do-response ( response -- ) : do-response ( response -- )
dup write-response dup write-response
request get method>> "HEAD" = request get method>> "HEAD" =
[ drop ] [ [ drop ] [ '[ , write-response-body ] [ http-error. ] recover ] if ;
'[
, write-response-body
] [
http-error.
] recover
] if ;
LOG: httpd-hit NOTICE LOG: httpd-hit NOTICE
@ -223,9 +57,7 @@ LOG: httpd-hit NOTICE
: init-request ( request -- ) : init-request ( request -- )
request set request set
H{ } clone responder-nesting set V{ } clone responder-nesting set ;
[ ] link-hook set
[ ] form-hook set ;
: dispatch-request ( request -- response ) : dispatch-request ( request -- response )
url>> path>> split-path main-responder get call-responder ; url>> path>> split-path main-responder get call-responder ;
@ -235,9 +67,7 @@ LOG: httpd-hit NOTICE
[ init-request ] [ init-request ]
[ log-request ] [ log-request ]
[ dispatch-request ] tri [ dispatch-request ] tri
] ] [ [ \ do-request log-error ] [ <500> ] bi ] recover ;
[ [ \ do-request log-error ] [ <500> ] bi ]
recover ;
: ?refresh-all ( -- ) : ?refresh-all ( -- )
development-mode get-global development-mode get-global
@ -254,8 +84,7 @@ LOG: httpd-hit NOTICE
: httpd ( port -- ) : httpd ( port -- )
dup integer? [ internet-server ] when dup integer? [ internet-server ] when
"http.server" latin1 "http.server" latin1 [ handle-client ] with-server ;
[ handle-client ] with-server ;
: httpd-main ( -- ) : httpd-main ( -- )
8888 httpd ; 8888 httpd ;

View File

@ -1,10 +1,15 @@
! Copyright (C) 2004, 2008 Slava Pestov. ! Copyright (C) 2004, 2008 Slava Pestov.
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: calendar io io.files kernel math math.order USING: calendar io io.files kernel math math.order
math.parser http http.server namespaces parser sequences strings math.parser namespaces parser sequences strings
assocs hashtables debugger http.mime sorting html.elements assocs hashtables debugger mime-types sorting logging
html.templates.fhtml logging calendar.format accessors calendar.format accessors
io.encodings.binary fry xml.entities destructors urls ; io.encodings.binary fry xml.entities destructors urls
html.elements html.templates.fhtml
http
http.server
http.server.responses
http.server.redirection ;
IN: http.server.static IN: http.server.static
! special maps mime types to quots with effect ( path -- ) ! special maps mime types to quots with effect ( path -- )
@ -17,12 +22,6 @@ TUPLE: file-responder root hook special allow-listings ;
2drop t 2drop t
] if ; ] if ;
: <304> ( -- response )
304 "Not modified" <trivial-response> ;
: <403> ( -- response )
403 "Forbidden" <trivial-response> ;
: <file-responder> ( root hook -- responder ) : <file-responder> ( root hook -- responder )
file-responder new file-responder new
swap >>hook swap >>hook
@ -85,7 +84,7 @@ TUPLE: file-responder root hook special allow-listings ;
find-index [ serve-file ] [ list-directory ] ?if find-index [ serve-file ] [ list-directory ] ?if
] [ ] [
drop drop
request get url>> clone [ "/" append ] change-path <redirect> request get url>> clone [ "/" append ] change-path <permanent-redirect>
] if ; ] if ;
: serve-object ( filename -- response ) : serve-object ( filename -- response )

View File

@ -94,10 +94,10 @@ TUPLE: url protocol username password host port path query anchor ;
: <url> ( -- url ) url new ; : <url> ( -- url ) url new ;
: query-param ( request key -- value ) : query-param ( url key -- value )
swap query>> at ; swap query>> at ;
: set-query-param ( request value key -- request ) : set-query-param ( url value key -- url )
'[ , , _ ?set-at ] change-query ; '[ , , _ ?set-at ] change-query ;
: parse-host ( string -- host port ) : parse-host ( string -- host port )

View File

@ -19,7 +19,7 @@ M: counter-app init-session* drop 0 count sset ;
: <display-action> ( -- action ) : <display-action> ( -- action )
<page-action> <page-action>
[ count sget "counter" set-value ] >>init [ count sget "counter" set-value ] >>init
"$counter-app/counter" >>template ; { counter-app "counter" } >>template ;
: <counter-app> ( -- responder ) : <counter-app> ( -- responder )
counter-app new-dispatcher counter-app new-dispatcher

View File

@ -4,6 +4,7 @@ USING: accessors kernel sequences assocs io.files io.sockets
io.server io.server
namespaces db db.sqlite smtp namespaces db db.sqlite smtp
http.server http.server
http.server.dispatchers
furnace.db furnace.db
furnace.flows furnace.flows
furnace.sessions furnace.sessions
@ -51,7 +52,7 @@ TUPLE: factor-website < dispatcher ;
allow-password-recovery allow-password-recovery
allow-edit-profile allow-edit-profile
<boilerplate> <boilerplate>
"$factor-website/page" >>template { factor-website "page" } >>template
<flows> <flows>
<sessions> <sessions>
test-db <db-persistence> ; test-db <db-persistence> ;

View File

@ -11,9 +11,9 @@
<t:a t:href="$pastebin/list">Pastes</t:a> <t:a t:href="$pastebin/list">Pastes</t:a>
| <t:a t:href="$pastebin/new-paste">New Paste</t:a> | <t:a t:href="$pastebin/new-paste">New Paste</t:a>
<t:if t:code="http.server.sessions:uid"> <t:if t:code="furnace.sessions:uid">
<t:if t:code="http.server.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if> </t:if>

View File

@ -3,14 +3,22 @@
USING: namespaces assocs sorting sequences kernel accessors USING: namespaces assocs sorting sequences kernel accessors
hashtables sequences.lib db.types db.tuples db combinators hashtables sequences.lib db.types db.tuples db combinators
calendar calendar.format math.parser rss urls xml.writer calendar calendar.format math.parser rss urls xml.writer
xmode.catalog validators html.components html.templates.chloe xmode.catalog validators
html.components
html.templates.chloe
http.server http.server
http.server.dispatchers
http.server.redirection
furnace
furnace.actions furnace.actions
furnace.auth furnace.auth
furnace.auth.login furnace.auth.login
furnace.boilerplate ; furnace.boilerplate
furnace.rss ;
IN: webapps.pastebin IN: webapps.pastebin
TUPLE: pastebin < dispatcher ;
! ! ! ! ! !
! DOMAIN MODEL ! DOMAIN MODEL
! ! ! ! ! !
@ -91,7 +99,7 @@ M: annotation entity-link
: <pastebin-action> ( -- action ) : <pastebin-action> ( -- action )
<page-action> <page-action>
[ pastes "pastes" set-value ] >>init [ pastes "pastes" set-value ] >>init
"$pastebin/pastebin" >>template ; { pastebin "pastebin" } >>template ;
: pastebin-feed-entries ( seq -- entries ) : pastebin-feed-entries ( seq -- entries )
<reversed> 20 short head [ <reversed> 20 short head [
@ -99,7 +107,7 @@ M: annotation entity-link
swap swap
[ summary>> >>title ] [ summary>> >>title ]
[ date>> >>pub-date ] [ date>> >>pub-date ]
[ entity-link adjust-url >>link ] [ entity-link adjust-url relative-to-request >>link ]
tri tri
] map ; ] map ;
@ -130,7 +138,7 @@ M: annotation entity-link
] nest-values ] nest-values
] >>init ] >>init
"$pastebin/paste" >>template ; { pastebin "paste" } >>template ;
: paste-feed-entries ( paste -- entries ) : paste-feed-entries ( paste -- entries )
fetch-annotations annotations>> pastebin-feed-entries ; fetch-annotations annotations>> pastebin-feed-entries ;
@ -139,7 +147,7 @@ M: annotation entity-link
feed new feed new
swap swap
[ "Paste " swap id>> number>string append >>title ] [ "Paste " swap id>> number>string append >>title ]
[ entity-link adjust-url >>link ] [ entity-link adjust-url relative-to-request >>link ]
[ paste-feed-entries >>entries ] [ paste-feed-entries >>entries ]
tri ; tri ;
@ -168,7 +176,9 @@ M: annotation entity-link
mode-names "modes" set-value mode-names "modes" set-value
] >>init ] >>init
"$pastebin/new-paste" >>template { pastebin "new-paste" } >>template
[ mode-names "modes" set-value ] >>validate
[ [
validate-entity validate-entity
@ -225,8 +235,6 @@ M: annotation entity-link
bi bi
] >>submit ; ] >>submit ;
TUPLE: pastebin < dispatcher ;
SYMBOL: can-delete-pastes? SYMBOL: can-delete-pastes?
can-delete-pastes? define-capability can-delete-pastes? define-capability
@ -242,7 +250,7 @@ can-delete-pastes? define-capability
<new-annotation-action> "new-annotation" add-responder <new-annotation-action> "new-annotation" add-responder
<delete-annotation-action> { can-delete-pastes? } <protected> "delete-annotation" add-responder <delete-annotation-action> { can-delete-pastes? } <protected> "delete-annotation" add-responder
<boilerplate> <boilerplate>
"$pastebin/pastebin-common" >>template ; { pastebin "pastebin-common" } >>template ;
: init-pastes-table \ paste ensure-table ; : init-pastes-table \ paste ensure-table ;

View File

@ -4,7 +4,7 @@
<p class="news"> <p class="news">
<strong><t:view t:component="title" /></strong> <br/> <strong><t:view t:component="title" /></strong> <br/>
<t:a value="link" t:session="none" class="more">Read More...</t:a> <t:a value="link" class="more">Read More...</t:a>
</p> </p>
</t:chloe> </t:chloe>

View File

@ -3,7 +3,7 @@
<t:chloe xmlns:t="http://factorcode.org/chloe/1.0"> <t:chloe xmlns:t="http://factorcode.org/chloe/1.0">
<h2 class="posting-title"> <h2 class="posting-title">
<t:a t:value="link" t:session="none"><t:view t:component="title" /></t:a> <t:a t:value="link"><t:view t:component="title" /></t:a>
</h2> </h2>
<p class="posting-body"> <p class="posting-body">
@ -11,7 +11,7 @@
</p> </p>
<p class="posting-date"> <p class="posting-date">
<t:a t:value="link" t:session="none"><t:view t:component="pub-date" /></t:a> <t:a t:value="link"><t:view t:component="pub-date" /></t:a>
</p> </p>
</t:chloe> </t:chloe>

View File

@ -6,7 +6,7 @@
<p class="news"> <p class="news">
<strong><t:view t:component="title" /></strong> <br/> <strong><t:view t:component="title" /></strong> <br/>
<t:a value="link" t:session="none" class="more">Read More...</t:a> <t:a value="link" class="more">Read More...</t:a>
</p> </p>
</t:bind-each> </t:bind-each>

View File

@ -9,8 +9,8 @@
| <t:a t:href="$planet-factor/feed.xml">Atom Feed</t:a> | <t:a t:href="$planet-factor/feed.xml">Atom Feed</t:a>
| <t:a t:href="$planet-factor/admin">Admin</t:a> | <t:a t:href="$planet-factor/admin">Admin</t:a>
<t:if t:code="http.server.sessions:uid"> <t:if t:code="furnace.sessions:uid">
<t:if t:code="http.server.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if> </t:if>

View File

@ -7,12 +7,19 @@ html.components
rss urls xml.writer rss urls xml.writer
validators validators
http.server http.server
http.server.dispatchers
furnace
furnace.actions furnace.actions
furnace.boilerplate furnace.boilerplate
furnace.auth.login furnace.auth.login
furnace.auth ; furnace.auth
furnace.rss ;
IN: webapps.planet IN: webapps.planet
TUPLE: planet-factor < dispatcher ;
TUPLE: planet-factor-admin < dispatcher ;
TUPLE: blog id name www-url feed-url ; TUPLE: blog id name www-url feed-url ;
M: blog link-title name>> ; M: blog link-title name>> ;
@ -58,7 +65,7 @@ posting "POSTINGS"
: <edit-blogroll-action> ( -- action ) : <edit-blogroll-action> ( -- action )
<page-action> <page-action>
[ blogroll "blogroll" set-value ] >>init [ blogroll "blogroll" set-value ] >>init
"$planet-factor/admin" >>template ; { planet-factor "admin" } >>template ;
: <planet-action> ( -- action ) : <planet-action> ( -- action )
<page-action> <page-action>
@ -67,7 +74,7 @@ posting "POSTINGS"
postings "postings" set-value postings "postings" set-value
] >>init ] >>init
"$planet-factor/planet" >>template ; { planet-factor "planet" } >>template ;
: planet-feed ( -- feed ) : planet-feed ( -- feed )
feed new feed new
@ -131,7 +138,7 @@ posting "POSTINGS"
: <new-blog-action> ( -- action ) : <new-blog-action> ( -- action )
<page-action> <page-action>
"$planet-factor/new-blog" >>template { planet-factor "new-blog" } >>template
[ validate-blog ] >>validate [ validate-blog ] >>validate
@ -155,7 +162,7 @@ posting "POSTINGS"
"id" value <blog> select-tuple from-object "id" value <blog> select-tuple from-object
] >>init ] >>init
"$planet-factor/edit-blog" >>template { planet-factor "edit-blog" } >>template
[ [
validate-integer-id validate-integer-id
@ -175,8 +182,6 @@ posting "POSTINGS"
tri tri
] >>submit ; ] >>submit ;
TUPLE: planet-factor-admin < dispatcher ;
: <planet-factor-admin> ( -- responder ) : <planet-factor-admin> ( -- responder )
planet-factor-admin new-dispatcher planet-factor-admin new-dispatcher
<edit-blogroll-action> "blogroll" add-main-responder <edit-blogroll-action> "blogroll" add-main-responder
@ -189,15 +194,13 @@ SYMBOL: can-administer-planet-factor?
can-administer-planet-factor? define-capability can-administer-planet-factor? define-capability
TUPLE: planet-factor < dispatcher ;
: <planet-factor> ( -- responder ) : <planet-factor> ( -- responder )
planet-factor new-dispatcher planet-factor new-dispatcher
<planet-action> "list" add-main-responder <planet-action> "list" add-main-responder
<feed-action> "feed.xml" add-responder <feed-action> "feed.xml" add-responder
<planet-factor-admin> { can-administer-planet-factor? } <protected> "admin" add-responder <planet-factor-admin> { can-administer-planet-factor? } <protected> "admin" add-responder
<boilerplate> <boilerplate>
"$planet-factor/planet-common" >>template ; { planet-factor "planet-common" } >>template ;
: start-update-task ( db params -- ) : start-update-task ( db params -- )
'[ , , [ update-cached-postings ] with-db ] 10 minutes every drop ; '[ , , [ update-cached-postings ] with-db ] 10 minutes every drop ;

View File

@ -11,7 +11,7 @@
<t:bind-each t:name="postings"> <t:bind-each t:name="postings">
<h2 class="posting-title"> <h2 class="posting-title">
<t:a t:value="link" t:session="none"><t:label t:name="title" /></t:a> <t:a t:value="link"><t:label t:name="title" /></t:a>
</h2> </h2>
<p class="posting-body"> <p class="posting-body">
@ -19,7 +19,7 @@
</p> </p>
<p class="posting-date"> <p class="posting-date">
<t:a t:value="link" t:session="none"><t:label t:name="pub-date" /></t:a> <t:a t:value="link"><t:label t:name="pub-date" /></t:a>
</p> </p>
</t:bind-each> </t:bind-each>

View File

@ -4,15 +4,19 @@ USING: accessors kernel sequences namespaces
db db.types db.tuples validators hashtables urls db db.types db.tuples validators hashtables urls
html.components html.components
html.templates.chloe html.templates.chloe
http.server
http.server.dispatchers
furnace
furnace.sessions furnace.sessions
furnace.boilerplate furnace.boilerplate
furnace.auth furnace.auth
furnace.actions furnace.actions
furnace.db furnace.db
furnace.auth.login furnace.auth.login ;
http.server ;
IN: webapps.todo IN: webapps.todo
TUPLE: todo-list < dispatcher ;
TUPLE: todo uid id priority summary description ; TUPLE: todo uid id priority summary description ;
todo "TODO" todo "TODO"
@ -38,7 +42,7 @@ todo "TODO"
"id" value <todo> select-tuple from-object "id" value <todo> select-tuple from-object
] >>init ] >>init
"$todo-list/view-todo" >>template ; { todo-list "view-todo" } >>template ;
: validate-todo ( -- ) : validate-todo ( -- )
{ {
@ -51,7 +55,7 @@ todo "TODO"
<page-action> <page-action>
[ 0 "priority" set-value ] >>init [ 0 "priority" set-value ] >>init
"$todo-list/new-todo" >>template { todo-list "new-todo" } >>template
[ validate-todo ] >>validate [ validate-todo ] >>validate
@ -75,7 +79,7 @@ todo "TODO"
"id" value <todo> select-tuple from-object "id" value <todo> select-tuple from-object
] >>init ] >>init
"$todo-list/edit-todo" >>template { todo-list "edit-todo" } >>template
[ [
validate-integer-id validate-integer-id
@ -107,9 +111,7 @@ todo "TODO"
: <list-action> ( -- action ) : <list-action> ( -- action )
<page-action> <page-action>
[ f <todo> select-tuples "items" set-value ] >>init [ f <todo> select-tuples "items" set-value ] >>init
"$todo-list/todo-list" >>template ; { todo-list "todo-list" } >>template ;
TUPLE: todo-list < dispatcher ;
: <todo-list> ( -- responder ) : <todo-list> ( -- responder )
todo-list new-dispatcher todo-list new-dispatcher
@ -119,5 +121,5 @@ TUPLE: todo-list < dispatcher ;
<edit-action> "edit" add-responder <edit-action> "edit" add-responder
<delete-action> "delete" add-responder <delete-action> "delete" add-responder
<boilerplate> <boilerplate>
"$todo-list/todo" >>template { todo-list "todo" } >>template
f <protected> ; f <protected> ;

View File

@ -8,7 +8,7 @@
<t:a t:href="$todo-list/list">List Items</t:a> <t:a t:href="$todo-list/list">List Items</t:a>
| <t:a t:href="$todo-list/new">Add Item</t:a> | <t:a t:href="$todo-list/new">Add Item</t:a>
<t:if t:code="http.server.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if> </t:if>

View File

@ -4,6 +4,7 @@ USING: kernel sequences accessors namespaces combinators words
assocs db.tuples arrays splitting strings validators urls assocs db.tuples arrays splitting strings validators urls
html.elements html.elements
html.components html.components
furnace
furnace.boilerplate furnace.boilerplate
furnace.auth.providers furnace.auth.providers
furnace.auth.providers.db furnace.auth.providers.db
@ -11,9 +12,12 @@ furnace.auth.login
furnace.auth furnace.auth
furnace.sessions furnace.sessions
furnace.actions furnace.actions
http.server ; http.server
http.server.dispatchers ;
IN: webapps.user-admin IN: webapps.user-admin
TUPLE: user-admin < dispatcher ;
: word>string ( word -- string ) : word>string ( word -- string )
[ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ; [ word-vocabulary ] [ drop ":" ] [ word-name ] tri 3append ;
@ -29,7 +33,7 @@ IN: webapps.user-admin
: <user-list-action> ( -- action ) : <user-list-action> ( -- action )
<page-action> <page-action>
[ f <user> select-tuples "users" set-value ] >>init [ f <user> select-tuples "users" set-value ] >>init
"$user-admin/user-list" >>template ; { user-admin "user-list" } >>template ;
: init-capabilities ( -- ) : init-capabilities ( -- )
capabilities get words>strings "capabilities" set-value ; capabilities get words>strings "capabilities" set-value ;
@ -46,7 +50,7 @@ IN: webapps.user-admin
init-capabilities init-capabilities
] >>init ] >>init
"$user-admin/new-user" >>template { user-admin "new-user" } >>template
[ [
init-capabilities init-capabilities
@ -94,7 +98,7 @@ IN: webapps.user-admin
capabilities get words>strings "capabilities" set-value capabilities get words>strings "capabilities" set-value
] >>init ] >>init
"$user-admin/edit-user" >>template { user-admin "edit-user" } >>template
[ [
init-capabilities init-capabilities
@ -140,8 +144,6 @@ IN: webapps.user-admin
URL" $user-admin" <redirect> URL" $user-admin" <redirect>
] >>submit ; ] >>submit ;
TUPLE: user-admin < dispatcher ;
SYMBOL: can-administer-users? SYMBOL: can-administer-users?
can-administer-users? define-capability can-administer-users? define-capability
@ -153,7 +155,7 @@ can-administer-users? define-capability
<edit-user-action> "edit" add-responder <edit-user-action> "edit" add-responder
<delete-user-action> "delete" add-responder <delete-user-action> "delete" add-responder
<boilerplate> <boilerplate>
"$user-admin/user-admin" >>template { user-admin "user-admin" } >>template
{ can-administer-users? } <protected> ; { can-administer-users? } <protected> ;
: make-admin ( username -- ) : make-admin ( username -- )

View File

@ -6,7 +6,7 @@
<t:a t:href="$user-admin">List Users</t:a> <t:a t:href="$user-admin">List Users</t:a>
| <t:a t:href="$user-admin/new">Add User</t:a> | <t:a t:href="$user-admin/new">Add User</t:a>
<t:if t:code="http.server.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if> </t:if>

View File

@ -10,9 +10,9 @@
| <t:a t:href="$wiki/articles">All Articles</t:a> | <t:a t:href="$wiki/articles">All Articles</t:a>
| <t:a t:href="$wiki/changes">Recent Changes</t:a> | <t:a t:href="$wiki/changes">Recent Changes</t:a>
<t:if t:code="http.server.sessions:uid"> <t:if t:code="furnace.sessions:uid">
<t:if t:code="http.server.auth.login:allow-edit-profile?"> <t:if t:code="furnace.auth.login:allow-edit-profile?">
| <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a> | <t:a t:href="$login/edit-profile" t:flow="begin">Edit Profile</t:a>
</t:if> </t:if>

View File

@ -4,6 +4,8 @@ USING: accessors kernel hashtables calendar
namespaces splitting sequences sorting math.order namespaces splitting sequences sorting math.order
html.components html.components
http.server http.server
http.server.dispatchers
furnace
furnace.actions furnace.actions
furnace.auth furnace.auth
furnace.auth.login furnace.auth.login
@ -12,6 +14,8 @@ validators
db.types db.tuples lcs farkup urls ; db.types db.tuples lcs farkup urls ;
IN: webapps.wiki IN: webapps.wiki
TUPLE: wiki < dispatcher ;
TUPLE: article title revision ; TUPLE: article title revision ;
article "ARTICLES" { article "ARTICLES" {
@ -64,7 +68,7 @@ revision "REVISIONS" {
[ [
"title" value dup <article> select-tuple [ "title" value dup <article> select-tuple [
revision>> <revision> select-tuple from-object revision>> <revision> select-tuple from-object
"$wiki/view" <chloe-content> { wiki "view" } <chloe-content>
] [ ] [
<url> <url>
"$wiki/edit" >>path "$wiki/edit" >>path
@ -81,7 +85,7 @@ revision "REVISIONS" {
select-tuple from-object select-tuple from-object
] >>init ] >>init
"$wiki/view" >>template ; { wiki "view" } >>template ;
: add-revision ( revision -- ) : add-revision ( revision -- )
[ insert-tuple ] [ insert-tuple ]
@ -102,7 +106,7 @@ revision "REVISIONS" {
] when* ] when*
] >>init ] >>init
"$wiki/edit" >>template { wiki "edit" } >>template
[ [
validate-title validate-title
@ -131,7 +135,7 @@ revision "REVISIONS" {
"revisions" set-value "revisions" set-value
] >>init ] >>init
"$wiki/revisions" >>template ; { wiki "revisions" } >>template ;
: <rollback-action> ( -- action ) : <rollback-action> ( -- action )
<action> <action>
@ -158,7 +162,7 @@ revision "REVISIONS" {
"changes" set-value "changes" set-value
] >>init ] >>init
"$wiki/changes" >>template ; { wiki "changes" } >>template ;
: <delete-action> ( -- action ) : <delete-action> ( -- action )
<action> <action>
@ -185,7 +189,7 @@ revision "REVISIONS" {
2bi 2bi
] >>init ] >>init
"$wiki/diff" >>template ; { wiki "diff" } >>template ;
: <list-articles-action> ( -- action ) : <list-articles-action> ( -- action )
<page-action> <page-action>
@ -195,7 +199,7 @@ revision "REVISIONS" {
"articles" set-value "articles" set-value
] >>init ] >>init
"$wiki/articles" >>template ; { wiki "articles" } >>template ;
: <user-edits-action> ( -- action ) : <user-edits-action> ( -- action )
<page-action> <page-action>
@ -205,9 +209,7 @@ revision "REVISIONS" {
select-tuples "user-edits" set-value select-tuples "user-edits" set-value
] >>init ] >>init
"$wiki/user-edits" >>template ; { wiki "user-edits" } >>template ;
TUPLE: wiki < dispatcher ;
: <wiki> ( -- dispatcher ) : <wiki> ( -- dispatcher )
wiki new-dispatcher wiki new-dispatcher
@ -223,4 +225,4 @@ TUPLE: wiki < dispatcher ;
<edit-article-action> { } <protected> "edit" add-responder <edit-article-action> { } <protected> "edit" add-responder
<delete-action> { } <protected> "delete" add-responder <delete-action> { } <protected> "delete" add-responder
<boilerplate> <boilerplate>
"$wiki/wiki-common" >>template ; { wiki "wiki-common" } >>template ;

View File

@ -1,13 +1,12 @@
IN: furnace.callbacks USING: furnace furnace.actions furnace.callbacks accessors
USING: furnace.actions furnace.callbacks accessors http http.server http.server.responses tools.test
http.server http tools.test namespaces io fry sequences namespaces io fry sequences
splitting kernel hashtables continuations ; splitting kernel hashtables continuations ;
IN: furnace.callbacks.tests
[ 123 ] [ [ 123 ] [
[ [
init-request <request> "GET" >>method init-request
<request> "GET" >>method request set
[ [
exit-continuation set exit-continuation set
{ } { }
@ -19,8 +18,6 @@ splitting kernel hashtables continuations ;
] unit-test ] unit-test
[ [
init-request
<action> [ <action> [
[ [
"hello" print "hello" print
@ -32,9 +29,11 @@ splitting kernel hashtables continuations ;
<callback-responder> "r" set <callback-responder> "r" set
[ 123 ] [ [ 123 ] [
<request> init-request
[ [
exit-continuation set exit-continuation set
<request> "GET" >>method request set <request> "GET" >>method init-request
{ } "r" get call-responder { } "r" get call-responder
] callcc1 ] callcc1
@ -42,9 +41,9 @@ splitting kernel hashtables continuations ;
<request> <request>
"GET" >>method "GET" >>method
swap cont-id associate >>query dup url>> rot cont-id associate >>query drop
"/" >>path dup url>> "/" >>path drop
request set init-request
[ [
exit-continuation set exit-continuation set
@ -55,9 +54,9 @@ splitting kernel hashtables continuations ;
! get-post-get ! get-post-get
<request> <request>
"GET" >>method "GET" >>method
swap "location" header "=" last-split1 nip cont-id associate >>query dup url>> rot "location" header query>> >>query drop
"/" >>path dup url>> "/" >>path drop
request set init-request
[ [
exit-continuation set exit-continuation set

View File

@ -3,7 +3,8 @@
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: http http.server io kernel math namespaces USING: http http.server io kernel math namespaces
continuations calendar sequences assocs hashtables continuations calendar sequences assocs hashtables
accessors arrays alarms quotations combinators fry assocs.lib ; accessors arrays alarms quotations combinators fry
http.server.redirection furnace assocs.lib urls ;
IN: furnace.callbacks IN: furnace.callbacks
SYMBOL: responder SYMBOL: responder
@ -11,9 +12,6 @@ SYMBOL: responder
TUPLE: callback-responder responder callbacks ; TUPLE: callback-responder responder callbacks ;
: <callback-responder> ( responder -- responder' ) : <callback-responder> ( responder -- responder' )
#! A continuation responder is a special type of session
#! manager. However it works entirely differently from
#! the URL and cookie session managers.
H{ } clone callback-responder boa ; H{ } clone callback-responder boa ;
TUPLE: callback cont quot expires alarm responder ; TUPLE: callback cont quot expires alarm responder ;
@ -44,7 +42,7 @@ TUPLE: callback cont quot expires alarm responder ;
: register-callback ( cont quot expires? -- id ) : register-callback ( cont quot expires? -- id )
<callback> callback-responder get callbacks>> set-at-unique ; <callback> callback-responder get callbacks>> set-at-unique ;
: forward-to-url ( url query -- * ) : forward-to-url ( url -- * )
#! When executed inside a 'show' call, this will force a #! When executed inside a 'show' call, this will force a
#! HTTP 302 to occur to instruct the browser to forward to #! HTTP 302 to occur to instruct the browser to forward to
#! the request URL. #! the request URL.
@ -56,7 +54,8 @@ TUPLE: callback cont quot expires alarm responder ;
#! When executed inside a 'show' call, this will force a #! When executed inside a 'show' call, this will force a
#! HTTP 302 to occur to instruct the browser to forward to #! HTTP 302 to occur to instruct the browser to forward to
#! the request URL. #! the request URL.
f swap cont-id associate forward-to-url ; <url>
swap cont-id set-query-param forward-to-url ;
: restore-request ( pair -- ) : restore-request ( pair -- )
first3 exit-continuation set request set call ; first3 exit-continuation set request set call ;
@ -94,7 +93,7 @@ SYMBOL: current-show
call exit-with ; inline call exit-with ; inline
: resuming-callback ( responder request -- id ) : resuming-callback ( responder request -- id )
cont-id query-param swap callbacks>> at ; url>> cont-id query-param swap callbacks>> at ;
M: callback-responder call-responder* ( path responder -- response ) M: callback-responder call-responder* ( path responder -- response )
'[ '[