Skip to content

Commit

Permalink
moved range handling into separate vocab to make it reusable
Browse files Browse the repository at this point in the history
  • Loading branch information
x6j8x committed Aug 1, 2010
1 parent 3e35a6a commit 4c18d2c
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 106 deletions.
2 changes: 2 additions & 0 deletions machine/data/data.factor
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ io.encodings.utf8 kernel linked-assocs math namespaces
quotations uuid ;
IN: http.machine.data

CONSTANT: CRLF B{ 13 10 }

TUPLE: stream-body chunk next ;

: <stream-body> ( chunk next -- stream-body )
Expand Down
7 changes: 3 additions & 4 deletions machine/flow/flow.factor
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,9 @@ M: v3o18 decide
[ set-expires ]
[
[ response "accept-content-type" tx-metadata ] dip
[ (content-types-provided) at [ call( -- content ) >>body ] when* ]
[ drop >>content-type ] 3bi 2drop
! get content quot call content-types-provided
! call content quot and set response body
[ drop >>content-type ]
[ (content-types-provided) at [ call( -- content ) >>body ] when* ] 3bi
2drop
]
[ response code>> [ decide ] [ v3o18b decide ] if* ]
} cleave
Expand Down
2 changes: 1 addition & 1 deletion machine/machine.factor
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ TUPLE: machine-server < threaded-server dispatcher ;
utf8 >>content-encoding ; inline

: <500> ( error -- response )
<machine-response> 500 >>code
<machine-response> 500 >>code
f "server-keep-alive" set-tx-metadata
swap machine-development? get
[ error-response ] [ drop ] if ;
Expand Down
72 changes: 29 additions & 43 deletions machine/resource/static/static.factor
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
USING: accessors arrays assocs checksums checksums.sha
combinators.short-circuit formatting fry http.machine.data
http.machine.resource http.machine.util io io.encodings.binary
io.files io.files.info io.files.types io.pathnames kernel
literals locals math mime.types namespaces sequences ;
USING: accessors arrays assocs byte-arrays checksums
checksums.sha combinators.short-circuit formatting fry
http.machine.data http.machine.resource http.machine.util.byte-ranges io
io.encodings.binary io.files io.files.info
io.files.types io.pathnames io.streams.limited kernel literals
locals math math.parser mime.types namespaces sequences strings
uuid ;
FROM: ascii => >lower ;
IN: http.machine.resource.static

Expand All @@ -15,39 +17,28 @@ TUPLE: entry path info directory? ;

<PRIVATE

: copy-range ( resource range -- )
dup length 1 = [
2drop
] [
2drop
] if ;
: [partial-copy] ( start length -- quot )
'[
_ seek-absolute input-stream get
[ stream-seek ] keep _ stream-eofs limit
[ write ] each-stream-block
flush
] ; inline

M: static-file-resource copy-range ( byte-range resource -- )
swap [ entry>> path>> binary ] dip
>byte-range< over -
[partial-copy] with-file-reader ;

: copy-file ( resource -- )
entry>> path>> binary
[ [ write ] each-block ] with-file-reader ; inline

: range? ( -- ranges/f )
"range" get-request-header [ byte-ranges ] [ f ] if* ;

PREDICATE: from-to-range < byte-range { [ >byte-range< and ] [ >byte-range< [ 0 >= ] bi@ and ] } 1&& ;
PREDICATE: suffix-range < byte-range { [ start>> ] [ start>> 0 < ] } 1&& ;

GENERIC: check-range ( size range -- t/f )

M: from-to-range check-range
second <= ;
M: static-file-resource ranges-satified? ( ranges resource -- ranges/f )
entry>> info>> size>> '[ _ swap end>> >= ] dupd all? [ ] [ drop f ] if ;

M: suffix-range check-range
first neg <= ;

M: object check-range
2drop f ;

: ranges-satified? ( ranges resource -- ranges/f )
B entry>> info>> size>> '[ _ swap check-range ] dupd all? [ ] [ drop f ] if ;

: write-file ( resource -- )
range? [ copy-range ] [ copy-file ] if* ;
M: static-file-resource content-size ( resource -- size )
entry>> info>> size>> ;

: set-file-info ( entry path -- entry )
dup exists? [
Expand All @@ -63,15 +54,10 @@ M: object check-range
: content-type ( path -- ct )
file-extension >lower mime-types at "application/octet-stream" or ;

:: handle-range-request ( ranges resource -- quot/f )
ranges resource ranges-satified?
[ [ [ resource ranges copy-range ] ] ]
[ response 416 >>code drop f ] if ; inline

:: [content-writer] ( resource -- quot )
range? :> ranges
ranges [ ranges resource handle-range-request ]
[ [ [ resource copy-file ] ] ] if ; inline
: [content-writer] ( resource -- quot/f )
dup entry>> info>> size>> range-request?
[ swap [range-request-handler] ]
[ '[ [ _ copy-file ] ] ] if* ; inline

PRIVATE>

Expand Down Expand Up @@ -105,5 +91,5 @@ M: static-file-resource moved-permanently?
M: static-file-resource finish-request
"bytes" "Accept-Ranges" set-response-header
"range-request" tx-metadata [
response 206 >>code drop
] when drop ;
drop response 206 >>code drop
] [ entry>> [ [ response ] dip info>> size>> >>size drop ] when* ] if ;
12 changes: 12 additions & 0 deletions machine/util/byte-ranges/byte-ranges-tests.factor
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
USING: http.machine.util.byte-ranges kernel tools.test ;

IN: http.machine.util.byte-ranges.tests

[ V{ T{ byte-range f 0 20 } } ] [ 1024 "bytes=0-20" byte-ranges ] unit-test

[ V{ T{ byte-range f 1000 1024 } } ] [ 1024 "bytes=-24" byte-ranges ] unit-test

[ V{ T{ byte-range f 0 20 } T{ byte-range f 1000 1024 } } ]
[ 1024 "bytes=5-10,0-15,14-20,-24" byte-ranges ] unit-test

[ V{ T{ byte-range f 1000 1024 } } ] [ 1024 "bytes=1000-" byte-ranges ] unit-test
198 changes: 198 additions & 0 deletions machine/util/byte-ranges/byte-ranges.factor
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
USING: accessors ascii byte-arrays combinators
combinators.short-circuit continuations fry http.machine.data
http.machine.resource.static.private io io.encodings.binary
io.files kernel layouts locals make math math.order math.parser
peg peg.parsers prettyprint sequences sorting strings uuid ;
FROM: sequences.deep => flatten ;
IN: http.machine.util.byte-ranges

TUPLE: byte-range start end ;

: >byte-range< ( byte-range -- start end )
[ start>> ] [ end>> ] bi ; inline

: <byte-range> ( start end -- byte-range )
byte-range boa ;

<PRIVATE

! ranges-specifier = byte-ranges-specifier
! byte-ranges-specifier = bytes-unit "=" byte-range-set
! byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
! byte-range-spec = first-byte-pos "-" [last-byte-pos]
! first-byte-pos = 1*DIGIT
! last-byte-pos = 1*DIGIT
! suffix-byte-range-spec = "-" suffix-length
! suffix-length = 1*DIGIT

: 'space' ( -- parser )
[ " \t" member? ] satisfy repeat0 hide ;

: 'byte-ranges-specifier' ( -- parser )
"bytes" token hide ;

: 'byte-pos' ( -- parser )
[ digit? ] satisfy repeat1 [ string>number ] action ;

: 'comma' ( -- parser )
"," token hide ;

: 'equals' ( -- parser )
"=" token hide ;

: 'separator' ( -- parse )
'equals' 'comma' 2choice ;

: 'suffix-byte-range-spec' ( -- parser )
[
'separator' ,
"-" token hide ,
'byte-pos' ,
'space' ,
] seq* [ first neg f <byte-range> ] action ;

: 'byte-range-spec' ( -- parser )
[
'separator' ,
'space' ,
'byte-pos' ,
'space' ,
"-" token hide ,
'byte-pos' optional ,
'space' ,
] seq* [ first2 <byte-range> ] action ;

PEG: parse-range-spec ( str -- ranges )
[
'space' ,
'byte-ranges-specifier' ,
'byte-range-spec' 'suffix-byte-range-spec' 2choice repeat1 ,
] seq* [ flatten ] action ;

SINGLETONS: +overlap+ +included+ ;

GENERIC: combine ( range range type -- range range/f )

M: +overlap+ combine
drop [ start>> ] [ end>> ] bi* <byte-range> f ;

M: +included+ combine
2drop f ;

M: f combine
drop ;

: overlap? ( range range -- type/f )
[ end>> ] [ start>> ] bi* >=
[ +overlap+ ] [ f ] if ;

: included? ( range range -- type/f )
{
[ [ start>> ] [ start>> ] bi* <= ]
[ [ end>> ] [ end>> ] bi* >= ]
} 2&& [ +included+ ] [ f ] if ;

: ?combine ( byte-range byte-range -- byte-range byte-range/f )
2dup { [ included? ] [ overlap? ] } 2|| combine ;

: convert-suffix ( range size -- range )
[ over start>> + >>start ] [ >>end ] bi ; inline

: convert-prefix ( range size -- range )
2dup [ start>> ] [ ] bi* <=
[ >>end ] [ drop most-positive-fixnum >>end ] if ; inline

: ?convert ( range size -- range )
{
{ [ over { [ end>> not ] [ start>> 0 < ] } 1&& ] [ convert-suffix ] }
{ [ over { [ end>> not ] [ start>> 0 > ] } 1&& ] [ convert-prefix ] }
[ drop ]
} cond ; inline

: sort-ranges ( size seq -- seq' )
swap
'[ _ ?convert ] map
[ [ start>> ] bi@ <=> ] sort ;

: retain ( seq elt -- seq )
over push ;

: consolidate ( seq -- seq' )
unclip-slice V{ } clone [ push ] keep
[
[ [ ] [ pop ] bi ] dip
?combine [ [ retain ] bi@ ] [ retain ] if*
] reduce ;

PRIVATE>

: byte-ranges ( size str -- seq )
[ parse-range-spec sort-ranges consolidate ]
[ 3drop f ] recover ;

: range-request? ( size -- ranges/f )
"range" get-request-header [ byte-ranges ] [ drop f ] if* ;

GENERIC: ranges-satified? ( ranges resource -- ranges/f )

GENERIC: copy-range ( byte-range object -- )

GENERIC: content-size ( object -- size )

<PRIVATE

: make-range ( byte-range object -- string )
[ >byte-range< [ number>string ] bi@ "-" glue ] dip
content-size number>string "/" glue
[ "bytes" ] dip " " glue ;

: write-boundary ( -- )
"--" "boundary" tx-metadata append >byte-array write CRLF write ;

: write-end-boundary ( -- )
"--" "boundary" tx-metadata "--" 3append >byte-array write CRLF write ;

: write-part-headers ( object byte-range -- )
"Content-Type: " "original-ct" tx-metadata append >byte-array write
CRLF write
[ "Content-Range: " ] 2dip make-range append >byte-array write
CRLF dup [ write ] bi@ ;

: write-range-part ( object byte-range -- )
write-boundary
[ write-part-headers ] [ swap copy-range ] 2bi
CRLF write ;

: copy-ranges ( ranges object -- )
over length 1 = [ [ first ] dip copy-range ] [
swap '[ _ swap write-range-part ] each
write-end-boundary
] if ;

: remember-ct ( -- )
response content-type>> "original-ct" set-tx-metadata ; inline

: boundary ( -- string )
uuid4 [ "boundary" set-tx-metadata ] keep ;

: make-multipart ( string -- )
[ response ] dip
[ "multipart/byteranges; boundary" ] dip "=" glue
>>content-type drop ;

: ?multipart ( ranges resource -- )
over length 1 > [
2drop remember-ct boundary make-multipart
] [
[ first ] dip make-range "Content-Range" set-response-header
] if ; inline

PRIVATE>

: [range-request-handler] ( ranges resource -- quot/f )
2dup ranges-satified?
[
t "range-request" set-tx-metadata
[ ?multipart ]
[ '[ [ _ _ copy-ranges ] ] ] 2bi
] [ 2drop response 416 >>code drop f ] if ; inline
Loading

0 comments on commit 4c18d2c

Please sign in to comment.