Working on new HTTP server

db4
Slava Pestov 2008-02-25 14:53:18 -06:00
parent 20bbb4b5ae
commit 6260cd3e5a
8 changed files with 566 additions and 164 deletions

View File

@ -16,13 +16,16 @@ IN: assocs.lib
: at-default ( key assoc -- value/key ) : at-default ( key assoc -- value/key )
dupd at [ nip ] when* ; dupd at [ nip ] when* ;
: replace-at ( assoc value key -- assoc )
>r >r dup r> 1vector r> rot set-at ;
: insert-at ( value key assoc -- ) : insert-at ( value key assoc -- )
[ ?push ] change-at ; [ ?push ] change-at ;
: peek-at* ( key assoc -- obj ? ) : peek-at* ( assoc key -- obj ? )
at* dup [ >r peek r> ] when ; swap at* dup [ >r peek r> ] when ;
: peek-at ( key assoc -- obj ) : peek-at ( assoc key -- obj )
peek-at* drop ; peek-at* drop ;
: >multi-assoc ( assoc -- new-assoc ) : >multi-assoc ( assoc -- new-assoc )

View File

@ -35,6 +35,17 @@ SYMBOL: current-action
SYMBOL: validators-errored SYMBOL: validators-errored
SYMBOL: validation-errors SYMBOL: validation-errors
: build-url ( str query-params -- newstr )
[
over %
dup assoc-empty? [
2drop
] [
CHAR: ? rot member? "&" "?" ? %
assoc>query %
] if
] "" make ;
: action-link ( query action -- url ) : action-link ( query action -- url )
[ [
"/responder/" % "/responder/" %

View File

@ -1,14 +1,26 @@
USING: http.client tools.test ; USING: http.client http.client.private http tools.test
tuple-syntax namespaces ;
[ "localhost" 80 ] [ "localhost" parse-host ] unit-test [ "localhost" 80 ] [ "localhost" parse-host ] unit-test
[ "localhost" 8888 ] [ "localhost:8888" parse-host ] unit-test [ "localhost" 8888 ] [ "localhost:8888" parse-host ] unit-test
[ "localhost:8888" "/foo" ] [ "http://localhost:8888/foo" parse-url ] unit-test [ "/foo" "localhost" 8888 ] [ "http://localhost:8888/foo" parse-url ] unit-test
[ "localhost:8888" "/" ] [ "http://localhost:8888" parse-url ] unit-test [ "/" "localhost" 8888 ] [ "http://localhost:8888" parse-url ] unit-test
[ 404 ] [ "HTTP/1.1 404 File not found" parse-response ] unit-test
[ 404 ] [ "404 File not found" parse-response ] unit-test
[ 200 ] [ "HTTP/1.0 200" parse-response ] unit-test
[ 200 ] [ "HTTP/1.0 200 Success" parse-response ] unit-test
[ "foo.txt" ] [ "http://www.paulgraham.com/foo.txt" download-name ] unit-test [ "foo.txt" ] [ "http://www.paulgraham.com/foo.txt" download-name ] unit-test
[ "foo.txt" ] [ "http://www.arcsucks.com/foo.txt?xxx" download-name ] unit-test [ "foo.txt" ] [ "http://www.arcsucks.com/foo.txt?xxx" download-name ] unit-test
[ "foo.txt" ] [ "http://www.arcsucks.com/foo.txt/" download-name ] unit-test [ "foo.txt" ] [ "http://www.arcsucks.com/foo.txt/" download-name ] unit-test
[ "www.arcsucks.com" ] [ "http://www.arcsucks.com////" download-name ] unit-test [ "www.arcsucks.com" ] [ "http://www.arcsucks.com////" download-name ] unit-test
[
TUPLE{ request
method: "GET"
host: "www.apple.com"
path: "/index.html"
port: 80
}
] [
[
"http://www.apple.com/index.html"
<get-request>
request-with-url
] with-scope
] unit-test

View File

@ -2,64 +2,73 @@
! See http://factorcode.org/license.txt for BSD license. ! See http://factorcode.org/license.txt for BSD license.
USING: assocs http kernel math math.parser namespaces sequences 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 continuations assocs.lib calendar ; splitting continuations assocs.lib calendar vectors hashtables
accessors ;
IN: http.client IN: http.client
: parse-host ( url -- host port ) : parse-url ( url -- resource host port )
#! Extract the host name and port number from an HTTP URL. "http://" ?head [ "Only http:// supported" throw ] unless
":" split1 [ string>number ] [ 80 ] if* ;
SYMBOL: domain
: parse-url ( url -- host resource )
dup "https://" head? [
"ssl not yet supported: " swap append throw
] when "http://" ?head drop
"/" split1 [ "/" swap append ] [ "/" ] if* "/" split1 [ "/" swap append ] [ "/" ] if*
>r dup empty? [ drop domain get ] [ dup domain set ] if r> ; swap parse-host ;
: parse-response ( line -- code ) <PRIVATE
"HTTP/" ?head [ " " split1 nip ] when
" " split1 drop string>number [
"Premature end of stream" throw
] unless* ;
: read-response ( -- code header ) : store-path ( request path -- request )
#! After sending a GET or POST we read a response line and "?" split1 >r >>path r> dup [ query>assoc ] when >>query ;
#! header.
flush readln parse-response read-header ;
: crlf "\r\n" write ; ! This is all pretty complex because it needs to handle
! HTTP redirects, which might be absolute or relative
: request-with-url ( url request -- request )
clone dup "request" set
swap parse-url >r >r store-path r> >>host r> >>port ;
: http-request ( host resource method -- ) DEFER: (http-request)
write bl write " HTTP/1.0" write crlf
"Host: " write write crlf ;
: get-request ( host resource -- ) : absolute-redirect ( url -- request )
"GET" http-request crlf ; "request" get request-with-url ;
DEFER: http-get-stream : relative-redirect ( path -- request )
"request" get swap store-path ;
: do-redirect ( code headers stream -- code headers stream ) : do-redirect ( response -- response stream )
#! Should this support Location: headers that are dup response-code 300 399 between? [
#! relative URLs? header>> "location" peek-at
pick 100 /i 3 = [ dup "http://" head? [
dispose "location" swap peek-at nip http-get-stream absolute-redirect
] when ; ] [
relative-redirect
] if "GET" >>method (http-request)
] [
stdio get
] if ;
: default-timeout 1 minutes over set-timeout ; : (http-request) ( request -- response stream )
dup host>> over port>> <inet> <client> stdio set
write-request flush read-response
do-redirect ;
: http-get-stream ( url -- code headers stream ) PRIVATE>
#! Opens a stream for reading from an HTTP URL.
parse-url over parse-host <inet> <client> [ : http-request ( url request -- response stream )
[ [ get-request read-response ] with-stream* ] keep [
default-timeout request-with-url
] [ ] [ dispose ] cleanup do-redirect ; [
(http-request)
1 minutes over set-timeout
] [ ] [ stdio get dispose ] cleanup
] with-scope ;
: <get-request> ( -- request )
request construct-empty
"GET" >>method ;
: http-get-stream ( url -- response stream )
<get-request> http-request ;
: success? ( code -- ? ) 200 = ; : success? ( code -- ? ) 200 = ;
: check-response ( code headers stream -- stream ) : check-response ( response stream -- stream )
nip swap success? swap code>> success?
[ dispose "HTTP download failed" throw ] unless ; [ dispose "HTTP download failed" throw ] unless ;
: http-get ( url -- string ) : http-get ( url -- string )
@ -70,23 +79,18 @@ DEFER: http-get-stream
: download-to ( url file -- ) : download-to ( url file -- )
#! Downloads the contents of a URL to a file. #! Downloads the contents of a URL to a file.
>r http-get-stream check-response swap http-get-stream check-response
r> <file-writer> stream-copy ; [ swap <file-writer> stream-copy ] with-disposal ;
: download ( url -- ) : download ( url -- )
dup download-name download-to ; dup download-name download-to ;
: post-request ( content-type content host resource -- ) : <post-request> ( content-type content -- request )
#! Note: It is up to the caller to url encode the content if request construct-empty
#! it is required according to the content-type. "POST" >>method
"POST" http-request [ swap >>post-data
"Content-Length: " write length number>string write crlf swap >>post-data-type ;
"Content-Type: " write url-encode write crlf
crlf
] keep write ;
: http-post ( content-type content url -- code headers string ) : http-post ( content-type content url -- response string )
#! Make a POST request. The content is URL encoded for you. #! The content is URL encoded for you.
parse-url over parse-host <inet> <client> [ -rot url-encode <post-request> http-request contents ;
post-request flush read-response stdio get contents
] with-stream ;

99
extra/http/http-tests.factor Normal file → Executable file
View File

@ -1,4 +1,5 @@
USING: http tools.test ; USING: http tools.test multiline tuple-syntax
io.streams.string kernel arrays splitting sequences ;
IN: temporary IN: temporary
[ "hello%20world" ] [ "hello world" url-encode ] unit-test [ "hello%20world" ] [ "hello world" url-encode ] unit-test
@ -16,3 +17,99 @@ IN: temporary
[ "%20%21%20" ] [ " ! " url-encode ] unit-test [ "%20%21%20" ] [ " ! " url-encode ] unit-test
[ "\u001234hi\u002045" ] [ "\u001234hi\u002045" url-encode url-decode ] unit-test [ "\u001234hi\u002045" ] [ "\u001234hi\u002045" url-encode url-decode ] unit-test
STRING: read-request-test-1
GET http://foo/bar HTTP/1.1
Some-Header: 1
Some-Header: 2
Content-Length: 4
blah
;
[
TUPLE{ request
method: "GET"
path: "bar"
query: f
version: "1.1"
header: H{ { "some-header" V{ "1" "2" } } { "content-length" V{ "4" } } }
post-data: "blah"
}
] [
read-request-test-1 [
read-request
] with-string-reader
] unit-test
STRING: read-request-test-1'
GET bar HTTP/1.1
content-length: 4
some-header: 1
some-header: 2
blah
;
read-request-test-1' 1array [
read-request-test-1
[ read-request ] with-string-reader
[ write-request ] with-string-writer
! normalize crlf
string-lines "\n" join
] unit-test
STRING: read-request-test-2
HEAD http://foo/bar HTTP/1.0
Host: www.sex.com
;
[
TUPLE{ request
method: "HEAD"
path: "bar"
query: f
version: "1.0"
header: H{ { "host" V{ "www.sex.com" } } }
host: "www.sex.com"
}
] [
read-request-test-2 [
read-request
] with-string-reader
] unit-test
STRING: read-response-test-1
HTTP/1.0 404 not found
Content-Type: text/html
blah
;
[
TUPLE{ response
version: "1.0"
code: 404
message: "not found"
header: H{ { "content-type" V{ "text/html" } } }
}
] [
read-response-test-1
[ read-response ] with-string-reader
] unit-test
STRING: read-response-test-1'
HTTP/1.0 404 not found
content-type: text/html
;
read-response-test-1' 1array [
read-response-test-1
[ read-response ] with-string-reader
[ write-response ] with-string-writer
! normalize crlf
string-lines "\n" join
] unit-test

View File

@ -1,19 +1,34 @@
! Copyright (C) 2003, 2007 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: hashtables io kernel math namespaces math.parser assocs USING: hashtables io io.streams.string kernel math namespaces
sequences strings splitting ascii io.encodings.utf8 assocs.lib math.parser assocs sequences strings splitting ascii
namespaces unicode.case ; io.encodings.utf8 assocs.lib namespaces unicode.case combinators
vectors sorting new-slots accessors calendar ;
IN: http IN: http
: http-port 80 ; inline
: crlf "\r\n" write ;
: header-line ( line -- ) : header-line ( line -- )
": " split1 dup [ swap >lower insert ] [ 2drop ] if ; ": " split1 dup [ swap >lower insert ] [ 2drop ] if ;
: (read-header) ( -- ) : read-header-line ( -- )
readln dup readln dup
empty? [ drop ] [ header-line (read-header) ] if ; empty? [ drop ] [ header-line read-header-line ] if ;
: read-header ( -- hash ) : read-header ( -- multi-assoc )
[ (read-header) ] H{ } make-assoc ; [ read-header-line ] H{ } make-assoc ;
: write-header ( multi-assoc -- )
>alist sort-keys
[
swap write ": " write {
{ [ dup number? ] [ number>string ] }
{ [ dup timestamp? ] [ timestamp>http-string ] }
{ [ dup string? ] [ ] }
} cond write crlf
] multi-assoc-each crlf ;
: url-quotable? ( ch -- ? ) : url-quotable? ( ch -- ? )
#! In a URL, can this character be used without #! In a URL, can this character be used without
@ -23,7 +38,7 @@ IN: http
over digit? or over digit? or
swap "/_-." member? or ; foldable swap "/_-." member? or ; foldable
: push-utf8 ( string -- ) : push-utf8 ( ch -- )
1string encode-utf8 [ CHAR: % , >hex 2 CHAR: 0 pad-left % ] each ; 1string encode-utf8 [ CHAR: % , >hex 2 CHAR: 0 pad-left % ] each ;
: url-encode ( str -- str ) : url-encode ( str -- str )
@ -58,17 +73,205 @@ IN: http
: url-decode ( str -- str ) : url-decode ( str -- str )
[ 0 swap url-decode-iter ] "" make decode-utf8 ; [ 0 swap url-decode-iter ] "" make decode-utf8 ;
: hash>query ( hash -- str ) : query>assoc ( query -- assoc )
dup [
"&" split [
"=" split1 [ dup [ url-decode ] when ] 2apply
] H{ } map>assoc
] when ;
: assoc>query ( hash -- str )
[ [ url-encode ] 2apply "=" swap 3append ] { } assoc>map [ [ url-encode ] 2apply "=" swap 3append ] { } assoc>map
"&" join ; "&" join ;
: build-url ( str query-params -- newstr ) TUPLE: request
host
port
method
path
query
version
header
post-data
post-data-type ;
: <request>
request construct-empty
"1.0" >>version
http-port >>port ;
: url>path ( url -- path )
url-decode "http://" ?head
[ "/" split1 "" or nip ] [ "/" ?head drop ] if ;
: read-method ( request -- request )
" " read-until [ "Bad request: method" throw ] unless
>>method ;
: read-query ( request -- request )
" " read-until
[ "Bad request: query params" throw ] unless
query>assoc >>query ;
: read-url ( request -- request )
" ?" read-until {
{ CHAR: \s [ dup empty? [ drop read-url ] [ url>path >>path ] if ] }
{ CHAR: ? [ url>path >>path read-query ] }
[ "Bad request: URL" throw ]
} case ;
: parse-version ( string -- version )
"HTTP/" ?head [ "Bad version" throw ] unless
dup { "1.0" "1.1" } member? [ "Bad version" throw ] unless ;
: read-request-version ( request -- request )
readln [ CHAR: \s = ] left-trim
parse-version
>>version ;
: read-request-header ( request -- request )
read-header >>header ;
SYMBOL: max-post-request
1024 256 * max-post-request set-global
: content-length ( header -- n )
"content-length" peek-at string>number dup [
dup max-post-request get > [
"content-length > max-post-request" throw
] when
] when ;
: read-post-data ( request -- request )
dup header>> content-length [ read >>post-data ] when* ;
: parse-host ( string -- host port )
"." ?tail drop ":" split1
[ string>number ] [ http-port ] if* ;
: extract-host ( request -- request )
dup header>> "host" peek-at parse-host >r >>host r> >>port ;
: extract-post-data-type ( request -- request )
dup header>> "content-type" peek-at >>post-data-type ;
: read-request ( -- request )
<request>
read-method
read-url
read-request-version
read-request-header
read-post-data
extract-host
extract-post-data-type ;
: write-method ( request -- request )
dup method>> write bl ;
: write-url ( request -- request )
dup path>> url-encode write
dup query>> dup assoc-empty? [ drop ] [
"?" write
assoc>query write
] if ;
: write-request-url ( request -- request )
write-url bl ;
: write-version ( request -- request )
"HTTP/" write dup request-version write crlf ;
: write-request-header ( request -- request )
dup header>> >hashtable
over host>> [ "host" replace-at ] when*
over post-data>> [ length "content-length" replace-at ] when*
over post-data-type>> [ "content-type" replace-at ] when*
write-header ;
: write-post-data ( request -- request )
dup post-data>> [ write ] when* ;
: write-request ( request -- )
write-method
write-url
write-version
write-request-header
write-post-data
flush
drop ;
: request-url ( request -- url )
[ [
over % dup host>> [
dup assoc-empty? [ "http://" write
2drop dup host>> url-encode write
] [ ":" write
CHAR: ? rot member? "&" "?" ? % dup port>> number>string write
hash>query % ] when
] if "/" write
] "" make ; write-url
drop
] with-string-writer ;
TUPLE: response
version
code
message
header ;
: <response>
response construct-empty
"1.0" >>version
H{ } clone >>header ;
: read-response-version
" " read-until
[ "Bad response: version" throw ] unless
parse-version
>>version ;
: read-response-code
" " read-until [ "Bad response: code" throw ] unless
string>number [ "Bad response: code" throw ] unless*
>>code ;
: read-response-message
readln >>message ;
: read-response-header
read-header >>header ;
: read-response ( -- response )
<response>
read-response-version
read-response-code
read-response-message
read-response-header ;
: write-response-version ( response -- response )
"HTTP/" write
dup version>> write bl ;
: write-response-code ( response -- response )
dup code>> number>string write bl ;
: write-response-message ( response -- response )
dup message>> write crlf ;
: write-response-header ( response -- response )
dup header>> write-header ;
: write-response ( respose -- )
write-response-version
write-response-code
write-response-message
write-response-header
flush
drop ;
: set-response-header ( response value key -- response )
pick header>> -rot replace-at drop ;
: set-content-type ( response content-type -- response )
"content-type" set-response-header ;

View File

@ -1,39 +1,45 @@
USING: webapps.file http.server.responders http USING: http.server tools.test kernel namespaces accessors
http.server namespaces io tools.test strings io.server new-slots assocs.lib io http math sequences ;
logging ;
IN: temporary IN: temporary
[ ] [ f [ "404 not found" httpd-error ] with-logging ] unit-test TUPLE: mock-responder ;
[ "inspect/global" ] [ "/inspect/global" trim-/ ] unit-test : <mock-responder> ( path -- responder )
<responder> mock-responder construct-delegate ;
[ "index.html" ] M: mock-responder do-responder
[ "http://www.jedit.org/index.html" url>path ] unit-test 2nip
path>> on
[ "Hello world" print ]
"text/plain" <content> ;
[ "foo/bar" ] : check-dispatch ( tag path -- ? )
[ "http://www.jedit.org/foo/bar" url>path ] unit-test over off
<request> swap default-host get call-responder
write-response call get ;
[ "" ] [
[ "http://www.jedit.org/" url>path ] unit-test "" <dispatcher>
"foo" <mock-responder> add-responder
"bar" <mock-responder> add-responder
"baz/" <dispatcher>
"123" <mock-responder> add-responder
"default" <mock-responder> >>default
add-responder
default-host set
[ "" ] [ t ] [ "foo" "foo" check-dispatch ] unit-test
[ "http://www.jedit.org" url>path ] 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 ] [ "123" "baz/123" check-dispatch ] unit-test
[ "foobar" ] [ t ] [
[ "foobar" secure-path ] unit-test <request>
"baz" >>path
[ f ] "baz" default-host get call-responder
[ "foobar/../baz" secure-path ] unit-test dup code>> 300 399 between? >r
header>> "location" peek-at "baz/" tail? r> and
[ ] [ f [ "GET ../index.html" parse-request ] with-logging ] unit-test nip
[ ] [ f [ "POO" parse-request ] with-logging ] unit-test ] unit-test
] with-scope
[ H{ { "Foo" "Bar" } } ] [ "Foo=Bar" query>hash ] unit-test
[ H{ { "Foo" "Bar" } { "Baz" "Quux" } } ]
[ "Foo=Bar&Baz=Quux" query>hash ] unit-test
[ H{ { "Baz" " " } } ]
[ "Baz=%20" query>hash ] unit-test
[ H{ { "Foo" f } } ] [ "Foo" query>hash ] unit-test

View File

@ -1,65 +1,131 @@
! 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: assocs kernel namespaces io io.timeouts strings splitting
threads http http.server.responders sequences prettyprint threads http sequences prettyprint io.server logging calendar
io.server logging calendar ; new-slots html.elements accessors math.parser combinators.lib ;
IN: http.server IN: http.server
: (url>path) ( uri -- path ) TUPLE: responder path directory ;
url-decode "http://" ?head [
"/" split1 dup "" ? nip
] when ;
: url>path ( uri -- path ) : <responder> ( path -- responder )
"?" split1 dup [ "/" ?tail responder construct-boa ;
>r (url>path) "?" r> 3append
] [
drop (url>path)
] if ;
: secure-path ( path -- path ) GENERIC: do-responder ( request path responder -- quot response )
".." over subseq? [ drop f ] when ;
: request-method ( cmd -- method ) TUPLE: trivial-responder quot response ;
H{
{ "GET" "get" }
{ "POST" "post" }
{ "HEAD" "head" }
} at "bad" or ;
: (handle-request) ( arg cmd -- method path host ) : <trivial-responder> ( quot response -- responder )
request-method dup "method" set swap trivial-responder construct-boa
prepare-url prepare-header host ; "" <responder> over set-delegate ;
: handle-request ( arg cmd -- ) M: trivial-responder do-responder
[ (handle-request) serve-responder ] with-scope ; 2nip dup quot>> swap response>> ;
: parse-request ( request -- ) : trivial-response-body ( code message -- )
" " split1 dup [ <html>
" HTTP" split1 drop url>path secure-path dup [ <body>
swap handle-request <h1> swap number>string write bl write </h1>
] [ </body>
2drop bad-request </html> ;
] if
] [
2drop bad-request
] if ;
\ parse-request NOTICE add-input-logging : <trivial-response> ( code message -- quot response )
[ [ trivial-response-body ] 2curry ] 2keep <response>
"text/html" set-content-type
swap >>message
swap >>code ;
: <404> ( -- quot response )
404 "Not Found" <trivial-response> ;
: <redirect> ( to code message -- quot response )
<trivial-response>
rot "location" set-response-header ;
: <permanent-redirect> ( to -- quot response )
301 "Moved Permanently" <redirect> ;
: <temporary-redirect> ( to -- quot response )
307 "Temporary Redirect" <redirect> ;
: <content> ( content-type -- response )
<response>
200 >>code
swap set-content-type ;
TUPLE: dispatcher responders default ;
: responder-matches? ( path responder -- ? )
path>> head? ;
TUPLE: no-/-responder ;
M: no-/-responder do-responder
2drop
dup path>> "/" append >>path
request-url <permanent-redirect> ;
: <no-/-responder> ( -- responder )
"" <responder> no-/-responder construct-delegate ;
<no-/-responder> no-/-responder set-global
: find-responder ( path dispatcher -- path responder )
>r "/" ?head drop r>
[ responders>> [ dupd responder-matches? ] find nip ] keep
default>> or [ path>> ?head drop ] keep ;
: no-trailing-/ ( path responder -- path responder )
over empty? over directory>> and
[ drop no-/-responder get-global ] when ;
: call-responder ( request path responder -- quot response )
no-trailing-/ do-responder ;
SYMBOL: 404-responder
<404> <trivial-responder> 404-responder set-global
M: dispatcher do-responder
find-responder call-responder ;
: <dispatcher> ( path -- dispatcher )
<responder>
dispatcher construct-delegate
404-responder get-global >>default
V{ } clone >>responders ;
: add-responder ( dispatcher responder -- dispatcher )
over responders>> push ;
SYMBOL: virtual-hosts
SYMBOL: default-host
virtual-hosts global [ drop H{ } clone ] cache drop
default-host global [ drop 404-responder ] cache drop
: find-virtual-host ( host -- responder )
virtual-hosts get at [ default-host get ] unless* ;
: handle-request ( request -- )
[
dup path>> over host>> find-virtual-host
call-responder
write-response
] keep method>> "HEAD" = [ drop ] [ call ] if ;
: default-timeout 1 minutes stdio get set-timeout ;
LOG: httpd-hit NOTICE
: log-request ( request -- )
{ method>> host>> path>> } map-exec-with httpd-hit ;
: httpd ( port -- ) : httpd ( port -- )
internet-server "http.server" [ internet-server "http.server" [
1 minutes stdio get set-timeout default-timeout
readln [ parse-request ] when* read-request dup log-request handle-request
] with-server ; ] with-server ;
: httpd-main ( -- ) 8888 httpd ; : httpd-main ( -- ) 8888 httpd ;
MAIN: httpd-main MAIN: httpd-main
! Load default webapps
USE: webapps.file
USE: webapps.callback
USE: webapps.continuation
USE: webapps.cgi