Skip to content

Commit

Permalink
For GETs on regular files:
Browse files Browse the repository at this point in the history
Added support for Content-Range, If-Range, If-Match, expanded support
for If-None-Match.


git-svn-id: https://erlyaws.svn.sourceforge.net/svnroot/erlyaws/trunk/yaws@468 9fbdc01b-0d2c-0410-bfb7-fb27d70d8b52
  • Loading branch information
carsten3347 committed Jul 30, 2003
1 parent 9f407ff commit 0a0a859
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 24 deletions.
1 change: 1 addition & 0 deletions include/yaws.hrl
Expand Up @@ -129,6 +129,7 @@
last_modified,
etag,
set_cookie,
content_range,
content_length,
content_type,
transfer_encoding,
Expand Down
53 changes: 51 additions & 2 deletions src/yaws.erl
Expand Up @@ -684,8 +684,38 @@ is_prefix(_,_) ->
false.


%% Split a string of words seperated by Sep into a list of words and
%% strip off white space.
%%
%% HTML semantics are used, such that empty words are omitted.


split_sep(undefined, _Sep) ->
[];
split_sep(L, Sep) ->
case lists:dropwhile(fun is_space/1, L) of
[] -> [];
[Sep|T] -> split_sep(T, Sep);
[C|T] -> split_sep(T, Sep, [C], [])
end.

split_sep([], _Sep, AccL) ->
lists:reverse(AccL);
split_sep([Sep|T], Sep, AccL) ->
split_sep(T, Sep, AccL);
split_sep([C|T], Sep, AccL) ->
split_sep(T, Sep, [C], AccL).

split_sep([], _Sep, AccW, AccL) ->
lists:reverse([lists:reverse(lists:dropwhile(fun is_space/1, AccW))|AccL]);
split_sep([Sep|Tail], Sep, AccW, AccL) ->
split_sep(lists:dropwhile(fun is_space/1, Tail),
Sep,
[lists:reverse(lists:dropwhile(fun is_space/1, AccW))|AccL]);
split_sep([C|Tail], Sep, AccW, AccL) ->
split_sep(Tail, Sep, [C|AccW], AccL).




%% imperative out header management
Expand Down Expand Up @@ -718,6 +748,9 @@ outh_clear_headers() ->


outh_set_static_headers(Req, UT, Headers) ->
outh_set_static_headers(Req, UT, Headers, all).

outh_set_static_headers(Req, UT, Headers, Range) ->
H = get(outh),
{DoClose, _Chunked} = dcc(Req, Headers),
H2 = H#outh{
Expand All @@ -727,12 +760,20 @@ outh_set_static_headers(Req, UT, Headers) ->
server = make_server_header(),
last_modified = make_last_modified_header(UT#urltype.finfo),
etag = make_etag_header(UT#urltype.finfo),
content_length = make_content_length_header(UT#urltype.finfo),
content_range = make_content_range_header(Range),
content_length = make_content_length_header(
case Range of
all ->
UT#urltype.finfo;
{fromto, From, To, _Tot} ->
To - From + 1
end
),
content_type = make_content_type_header(UT#urltype.mime),
connection = make_connection_close_header(DoClose),
doclose = DoClose,
contlen = (UT#urltype.finfo)#file_info.size
},
},
put(outh, H2).

outh_set_304_headers(Req, UT, Headers) ->
Expand Down Expand Up @@ -928,6 +969,13 @@ make_content_type_header(MimeType) ->
["Content-Type: ", MimeType, "\r\n"].


make_content_range_header(all) ->
undefined;
make_content_range_header({fromto, From, To, Tot}) ->
["Content-Range: bytes ",
integer_to_list(From), $-, integer_to_list(To),
$/, integer_to_list(Tot), $\r, $\n].

make_content_length_header(Size) when integer(Size) ->
["Content-Length: ", integer_to_list(Size), "\r\n"];
make_content_length_header(FI) ->
Expand Down Expand Up @@ -1011,6 +1059,7 @@ outh_serialize() ->
noundef(H#outh.allow),
noundef(H#outh.last_modified),
noundef(H#outh.etag),
noundef(H#outh.content_range),
noundef(H#outh.content_length),
noundef(H#outh.content_type),
noundef(H#outh.set_cookie),
Expand Down
198 changes: 176 additions & 22 deletions src/yaws_server.erl
Expand Up @@ -1203,20 +1203,57 @@ handle_ut(CliSock, GC, SC, Req, H, ARG, UT, N) ->
deliver_403(CliSock, Req, GC, SC);
regular ->
ETag = yaws:make_etag(UT#urltype.finfo),
case H#headers.if_none_match of
undefined ->
yaws:outh_set_static_headers(Req, UT, H),
deliver_file(CliSock, GC, SC, Req, UT);
ETag ->
yaws:outh_set_304_headers(Req, UT, H),
deliver_accumulated(CliSock, GC, SC),
case yaws:outh_get_doclose() of
true -> done;
false -> continue
end;
Range = case H#headers.if_range of
Range_etag = [$"|_] when Range_etag /= ETag ->
all;
_ ->
requested_range(H#headers.range,
(UT#urltype.finfo)#file_info.size)
end,
case Range of
error -> deliver_416(CliSock, Req, GC, SC,
(UT#urltype.finfo)#file_info.size);
_ ->
yaws:outh_set_static_headers(Req, UT, H),
deliver_file(CliSock, GC, SC, Req, UT)
case H#headers.if_none_match of
undefined ->
case H#headers.if_match of
undefined ->
yaws:outh_set_static_headers
(Req, UT, H, Range),
deliver_file
(CliSock, GC, SC, Req, UT, Range);

Line ->
case lists:member(ETag,
yaws:split_sep(
Line, $,)) of
true ->
yaws:outh_set_static_headers
(Req, UT, H, Range),
deliver_file(CliSock, GC, SC,
Req, UT, Range);
false ->
deliver_xxx(CliSock, Req, GC, SC,
412)
end
end;
Line ->
case lists:member(ETag,
yaws:split_sep(Line, $,)) of
true ->
yaws:outh_set_304_headers(Req, UT, H),
deliver_accumulated(CliSock, GC, SC),
case yaws:outh_get_doclose() of
true -> done;
false -> continue
end;
false ->
yaws:outh_set_static_headers
(Req, UT, H, Range),
deliver_file(CliSock, GC, SC, Req,
UT, Range)
end
end
end;
yaws ->
yaws:outh_set_dyn_headers(Req, H),
Expand Down Expand Up @@ -1432,6 +1469,25 @@ deliver_403(CliSock, Req, GC, SC) ->
% Forbidden
deliver_xxx(CliSock, Req, GC, SC, 403).

deliver_416(CliSock, _Req, GC, SC, Tot) ->
B = list_to_binary(["<html><h1>416 ",
yaws_api:code_to_phrase(416),
"</h1></html>"]),
H = #outh{status = 416,
doclose = true,
chunked = false,
server = yaws:make_server_header(),
connection = yaws:make_connection_close_header(true),
content_range = ["Content-Range: */",
integer_to_list(Tot), $\r, $\n],
content_length = yaws:make_content_length_header(size(B)),
contlen = size(B),
content_type = yaws:make_content_type_header("text/html")},
put(outh, H),
accumulate_content(B),
deliver_accumulated(CliSock, GC, SC),
done.

deliver_501(CliSock, Req, GC, SC) ->
% Not implemented
deliver_xxx(CliSock, Req, GC, SC, 501).
Expand Down Expand Up @@ -1979,27 +2035,98 @@ ut_close({bin, _}) ->
ut_close(Fd) ->
file:close(Fd).




deliver_file(CliSock, GC, SC, Req, UT) ->

parse_range(L, Tot) ->
case catch parse_range_throw(L, Tot) of
{'EXIT', _} ->
% error
error;
R -> R
end.

parse_range_throw(L, Tot) ->
case lists:splitwith(fun(C)->C /= $- end, L) of
{FromS, [$-|ToS]} ->
case FromS of
[] -> case list_to_integer(ToS) of
I when Tot >= I, I>0 ->
{fromto, Tot-I, Tot-1, Tot}
end;
_ -> case list_to_integer(FromS) of
From when From>=0, From < Tot ->
case ToS of
[] -> {fromto, From, Tot-1, Tot};
_ -> case list_to_integer(ToS) of
To when To<Tot ->
{fromto, From, To, Tot};
_ ->
{fromto, From, Tot-1, Tot}
end
end
end
end
end.


%% This is not exactly what the RFC describes, but we do not want to
%% deal with multipart/byteranges.
unite_ranges(all, _) ->
all;
unite_ranges(error, R) ->
R;
unite_ranges(_, all) ->
all;
unite_ranges(R, error) ->
R;
unite_ranges({fromto, F0, T0, Tot},{fromto,F1,T1, Tot}) ->
{fromto,
if F0 >= F1 -> F1;
true -> F0
end,
if T0 >= T1 -> T0;
true -> T1
end,
Tot
}.


%% ret: all | error | {fromto, From, To, Tot}
requested_range(RangeHeader, TotalSize) ->
case yaws:split_sep(RangeHeader, $,) of
["bytes="++H|T] ->
lists:foldl(fun(L, R)->
unite_ranges(parse_range(L, TotalSize), R)
end, parse_range(H, TotalSize), T);
_ -> all
end.


deliver_file(CliSock, GC, SC, Req, UT, Range) ->
if
binary(UT#urltype.data) ->
%% cached
deliver_small_file(CliSock, GC, SC, Req, UT);
deliver_small_file(CliSock, GC, SC, Req, UT, Range);
true ->
case (UT#urltype.finfo)#file_info.size of
N when N < GC#gconf.large_file_chunk_size ->
deliver_small_file(CliSock, GC, SC, Req, UT);
deliver_small_file(CliSock, GC, SC, Req, UT, Range);
_ ->
deliver_large_file(CliSock, GC, SC, Req, UT)
deliver_large_file(CliSock, GC, SC, Req, UT, Range)
end
end.

deliver_small_file(CliSock, GC, SC, Req, UT) ->
deliver_small_file(CliSock, GC, SC, Req, UT, Range) ->
?TC([{record, GC, gconf}, {record, SC, sconf}, {record, UT, urltype}]),
Fd = ut_open(UT),
Bin = ut_read(Fd),
case Range of
all ->
Bin = ut_read(Fd);
{fromto, From, To, Tot} ->
Length = To - From + 1,
<<_:From/binary, Bin:Length/binary, _/binary>> = ut_read(Fd)
end,
close_if_HEAD(Req, fun() ->
deliver_accumulated(CliSock, GC,SC),
ut_close(Fd), throw({ok, 1})
Expand All @@ -2014,15 +2141,15 @@ deliver_small_file(CliSock, GC, SC, Req, UT) ->
continue
end.

deliver_large_file(CliSock, GC, SC, Req, UT) ->
deliver_large_file(CliSock, GC, SC, Req, UT, Range) ->
?TC([{record, GC, gconf}, {record, SC, sconf}, {record, UT, urltype}]),
close_if_HEAD(Req, fun() ->
deliver_accumulated(CliSock, GC,SC),
throw({ok, 1})
end),
deliver_accumulated(CliSock, GC, SC),
{ok,Fd} = file:open(UT#urltype.fullpath, [raw, binary, read]),
send_file(CliSock, Fd, SC, GC),
send_file(CliSock, Fd, SC, GC, Range),
case yaws:outh_get_doclose() of
true ->
done;
Expand All @@ -2031,6 +2158,13 @@ deliver_large_file(CliSock, GC, SC, Req, UT) ->
end.


send_file(CliSock, Fd, SC, GC, all) ->
send_file(CliSock, Fd, SC, GC);
send_file(CliSock, Fd, SC, GC, {fromto, From, To, _Tot}) ->
file:position(Fd, {bof, From}),
send_file_range(CliSock, Fd, SC, GC, To - From + 1).


send_file(CliSock, Fd, SC, GC) ->
case file:read(Fd, GC#gconf.large_file_chunk_size) of
{ok, Bin} ->
Expand All @@ -2047,6 +2181,26 @@ send_file(CliSock, Fd, SC, GC) ->
end
end.

send_file_range(CliSock, Fd, SC, GC, Len) when Len > 0 ->
{ok, Bin} = file:read(Fd,
case GC#gconf.large_file_chunk_size of
S when S < Len -> S;
_ -> Len
end
),
send_file_chunk(Bin, CliSock, SC, GC),
send_file_range(CliSock, Fd, SC, GC, Len - size(Bin));
send_file_range(CliSock, Fd, SC, GC, 0) ->
file:close(Fd),
case yaws:outh_get_chunked() of
true ->
gen_tcp_send(CliSock, [crnl(), "0", crnl2()], SC, GC),
done;
false ->
done
end.


send_file_chunk(Bin, CliSock, SC, GC) ->
case yaws:outh_get_chunked() of
true ->
Expand Down

0 comments on commit 0a0a859

Please sign in to comment.