2005-11-25
2005-11-25 16:14:42 by Henrik Grubbström (Grubba) <grubba@grubba.org>
-
a5c6e92c7a734ace5b398b187663cbf8dd139314
(542 lines)
(+300/-242)
[
Show
| Annotate
]
Branch: 5.2
Lots of cleanups.
Added support for ranges and non-files.
Rev: server/protocols/http.pike:1.481
2:
// Modified by Francesco Chemolli to add throttling capabilities.
// Copyright © 1996 - 2004, Roxen IS.
- constant cvs_version = "$Id: http.pike,v 1.480 2005/11/24 17:41:26 grubba Exp $";
+ constant cvs_version = "$Id: http.pike,v 1.481 2005/11/25 16:14:42 grubba Exp $";
// #define REQUEST_DEBUG
#define MAGIC_ERROR
770:
case "request-range":
contents = lower_case(contents-" ");
- if(!search(contents, "bytes"))
+ if (has_prefix(contents, "bytes"))
// Only care about "byte" ranges.
misc->range = contents[6..];
break;
case "range":
contents = lower_case(contents-" ");
- if(!misc->range && !search(contents, "bytes"))
+ if (!misc->range && has_prefix(contents, "bytes"))
// Only care about "byte" ranges. Also the Request-Range header
// has precedence since Stupid Netscape (TM) sends both but can't
// handle multipart/byteranges but only multipart/x-byteranges.
1509:
array range_info = ({});
string type;
string stored_data = "";
- void create(mapping _file, mapping heads, array _ranges, object id)
+ void create(mapping _file, mapping heads, array _ranges,
+ array(string)|string t, object id)
{
file = _file->file;
len = _file->len;
- foreach(indices(heads), string h)
- {
- if(lower_case(h) == "content-type") {
- array(string)|string t = heads[h];
- m_delete(heads, h);
+
if (arrayp(t)) type = t[-1];
else type = t;
- }
- }
- if(id->request_headers["request-range"])
- heads["Content-Type"] = "multipart/x-byteranges; boundary=" BOUND;
- else
- heads["Content-Type"] = "multipart/byteranges; boundary=" BOUND;
+
ranges = _ranges;
int clen;
foreach(ranges, array range)
1533:
int rlen = 1+ range[1] - range[0];
string sep = sprintf("\r\n--" BOUND "\r\nContent-Type: %s\r\n"
"Content-Range: bytes %d-%d/%d\r\n\r\n",
- type||"application/octet-stream",
- @range, len);
+ type, @range, len);
clen += rlen + strlen(sep);
range_info += ({ ({ rlen, sep }) });
}
1680:
}
if (file) {
data = file->read(len);
- }
+ } else if (!data) data = "";
#ifdef CONNECTION_DEBUG
werror("HTTP: Response =================================================\n"
"%s\n",
1696:
do_log(s);
} else {
MY_TRACE_ENTER("Async write.", 0);
+ #ifdef CONNECTION_DEBUG
+ werror("HTTP: Response headers =========================================\n"
+ "%s\n",
+ replace(sprintf("%O", headers),
+ ({"\\r\\n", "\\n", "\\t"}),
+ ({"\n", "\n", "\t"})));
+ #else
+ REQUEST_WERR(sprintf("HTTP: Send headers %O", headers));
+ #endif
if (sizeof(headers))
send(headers);
- if (sizeof(data))
+ if (data && sizeof(data))
send(data, len);
if (file)
send(file, len);
1756:
{
misc->no_proto_cache = 1;
if(misc->error_code)
- file = Roxen.http_status(misc->error_code, errors[misc->error]);
+ file = Roxen.http_status(misc->error_code, errors[misc->error_code]);
else if(err = catch {
file = conf->error_file( this_object() );
})
1789:
// The full header block (prot + " " + code + head_string + variant_string).
string full_headers="";
+ #if 0
+ REQUEST_WERR(sprintf("HTTP: Sending result for prot:%O, method:%O, file:%O",
+ prot, method, file));
+ #endif
if(!file->raw && (prot != "HTTP/0.9"))
{
if (!sizeof (file) && multi_status)
file = multi_status->http_answer();
if (file->error == Protocols.HTTP.HTTP_NO_CONTENT) {
- #if 0
- // We actually give some content cf comment below.
- file->len = 2;
- file->data = "\r\n";
- #else
+
file->len = 0;
file->data = "";
- #endif /* 0 */
+
}
string head_status = file->rettext;
1825:
mapping(string:string) heads = make_response_headers (file);
mapping(string:string) variant_heads = ([ "Date":"",
+ "Content-Type":"",
"Content-Length":"",
"Connection":"", ]) & heads;
m_delete(heads, "Date");
-
+ m_delete(heads, "Content-Type");
m_delete(heads, "Content-Length");
m_delete(heads, "Connection");
1857:
{
/* ({ time, len }) */
array(int) since_info = Roxen.parse_since( since );
- // werror("since: %{%O, %}\n"
- // "lm: %O\n"
- // "cacheable: %O\n",
- // since_info,
- // misc->last_modified,
- // misc->cacheable);
+ // werror("since: %{%O, %}\n"
+ // "lm: %O\n"
+ // "cacheable: %O\n",
+ // since_info,
+ // misc->last_modified,
+ // misc->cacheable);
if ( ((since_info[0] >= misc->last_modified) &&
((since_info[1] == -1) || (since_info[1] == file->len)))
// never say 'not modified' if cacheable has been lowered.
&& (zero_type(misc->cacheable) ||
(misc->cacheable >= INITIAL_CACHEABLE))
// actually ok, or...
- // || ((misc->cacheable>0)
- // && (since_info[0] + misc->cacheable<= predef::time(1))
- // // cacheable, and not enough time has passed.
+ // || ((misc->cacheable>0)
+ // && (since_info[0] + misc->cacheable<= predef::time(1))
+ // // cacheable, and not enough time has passed.
)
{
conditional = conditional || 304;
1883:
// All conditionals apply.
file->error = conditional;
file->file = file->data = file->len = 0;
- } else if(misc->range && file->len && objectp(file->file) &&
- !file->data && (method == "GET" || method == "HEAD"))
- // Plain and simple file and a Range header. Let's play.
- // Also we only bother with 200-requests. Anything else should be
- // nicely and completely ignored. Also this is only used for GET and
- // HEAD requests.
- {
- // split the range header. If no valid ranges are found, ignore it.
- // If one is found, send that range. If many are found we need to
- // use a wrapper and send a multi-part message.
- array ranges = parse_range_header(file->len);
- if(ranges) // No incorrect syntax...
- {
- misc->no_proto_cache = 1;
- if(sizeof(ranges)) // And we have valid ranges as well.
- {
- file->error = 206; // 206 Partial Content
- if(sizeof(ranges) == 1)
- {
- heads["Content-Range"] = sprintf("bytes %d-%d/%d",
- @ranges[0], file->len);
- file->file->seek(ranges[0][0]);
- if(ranges[0][1] == (file->len - 1) &&
- GLOBVAR(RestoreConnLogFull))
- // Log continuations (ie REST in FTP), 'range XXX-'
- // using the entire length of the file, not just the
- // "sent" part. Ie add the "start" byte location when logging
- misc->_log_cheat_addition = ranges[0][0];
- file->len = ranges[0][1] - ranges[0][0]+1;
- } else {
- // Multiple ranges. Multipart reply and stuff needed.
- // We do this by replacing the file object with a wrapper.
- // Nice and handy.
- file->file = MultiRangeWrapper(file, heads, ranges, this_object());
+
}
- } else {
- // Got the header, but the specified ranges were out of bounds.
- // Reply with a 416 Requested Range not satisfiable.
- file->error = 416;
- heads["Content-Range"] = "*/"+file->len;
- if(method == "GET") {
- file->file = file->data = file->type = file->len = 0;
+
}
- }
- }
- }
- }
+
// FIXME: prot.
head_string = sprintf(" %s\r\n",
1988:
if (mixed err = catch(head_string += Roxen.make_http_headers(heads, 1)))
{
#ifdef DEBUG
- report_debug ("Roxen.make_http_headers failed: " +
+ report_debug("Roxen.make_http_headers failed: " +
describe_error (err));
#endif
foreach(heads; string x; string|array(string) val) {
if( !arrayp( val ) ) val = ({val});
foreach( val, string xx ) {
if (!stringp (xx) && catch {xx = (string) xx;})
- report_error ("Error in request for %O:\n"
+ report_error("Error in request for %O:\n"
"Invalid value for header %O: %O\n",
raw_url, x, xx);
else if (String.width (xx) > 8)
- report_error ("Error in request for %O:\n"
+ report_error("Error in request for %O:\n"
"Invalid widestring value for header %O: %O\n",
raw_url, x, xx);
else
2009:
head_string += "\r\n";
}
- variant_string = Roxen.make_http_headers(variant_heads);
- full_headers = prot + " " + file->error + head_string + variant_string;
- }
- else
- if(!file->type) file->type="text/plain";
-
- #if 0
- REQUEST_WERR(sprintf("HTTP: Sending result for prot:%O, method:%O, file:%O",
- prot, method, file));
- #endif
- MARK_FD("HTTP handled");
-
- if( (method!="HEAD") && (file->error!=204) && (file->error != 304) )
- // No data for the above...
+ if( (method == "HEAD") || (file->error == 204) || (file->error == 304) ||
+ (file->error < 200))
{
-
+ // RFC 2068 4.4.1
+ // Any response message which MUST NOT include a message-body
+ // (such as the 1xx, 204, and 304 responses and any response
+ // to a HEAD request) is always terminated by the first empty
+ // line after the header fields, regardless of the entity-header
+ // fields present in the message.
+
+ file->len = 1; // Keep those alive, please...
+ file->data = "";
+ file->file = 0;
+ } else {
#ifdef RAM_CACHE
if( (misc->cacheable > 0) && (file->data || file->file) &&
(prot != "HTTP/0.9") && !misc->no_proto_cache)
2039: Inside #if defined(RAM_CACHE)
string data = "";
if( file->data ) data = file->data[..file->len-1];
if( file->file ) data = file->file->read(file->len);
- MY_TRACE_ENTER (sprintf ("Storing in ram cache, entry: %O", raw_url), 0);
+ MY_TRACE_ENTER(sprintf("Storing in ram cache, entry: %O",
+ raw_url), 0);
MY_TRACE_LEAVE ("");
conf->datacache->set(raw_url, data,
([
2048: Inside #if defined(RAM_CACHE)
"etag":misc->etag,
"callbacks":misc->_cachecallbacks,
"len":file->len,
- // fix non-keep-alive when sending from cache
+
"raw":file->raw,
"error":file->error,
-
+ "type":variant_heads["Content-Type"],
"last_modified":misc->last_modified,
- "mtime":(file->stat && file->stat[ST_MTIME]),
+ "mtime":(file->stat &&
+ file->stat[ST_MTIME]),
"rf":realfile,
]),
misc->cacheable, misc->host);
- file = ([ "data":data, "raw":file->raw, "len":strlen(data) ]);
+ file = ([
+ "data":data,
+ "raw":file->raw,
+ "len":strlen(data),
+ "error":file->error,
+ ]);
}
}
#endif
-
+ if(misc->range && file->len && (method == "GET") &&
+ (file->error == 200) && (objectp(file->file) || file->data))
+ // Plain and simple file and a Range header. Let's play.
+ // Also we only bother with 200-requests. Anything else should be
+ // nicely and completely ignored.
+ // Also this is only used for GET requests.
+ {
+ // split the range header. If no valid ranges are found, ignore it.
+ // If one is found, send that range. If many are found we need to
+ // use a wrapper and send a multi-part message.
+ array ranges = parse_range_header(file->len);
+ if(ranges) // No incorrect syntax...
+ {
+ misc->no_proto_cache = 1;
+ if(sizeof(ranges)) // And we have valid ranges as well.
+ {
+ m_delete(variant_heads, "Content-Length");
+
+ file->error = 206; // 206 Partial Content
+ if(sizeof(ranges) == 1)
+ {
+ variant_heads["Content-Range"] = sprintf("bytes %d-%d/%d",
+ @ranges[0],
+ file->len);
+ if (objectp(file->file)) {
+ file->file->seek(ranges[0][0]);
+ } else {
+ file->data = file->data[ranges[0][0]..ranges[0][1]];
+ }
+ if(ranges[0][1] == (file->len - 1) &&
+ GLOBVAR(RestoreConnLogFull))
+ // Log continuations (ie REST in FTP), 'range XXX-'
+ // using the entire length of the file, not just the
+ // "sent" part. Ie add the "start" byte location when logging
+ misc->_log_cheat_addition = ranges[0][0];
+ file->len = ranges[0][1] - ranges[0][0]+1;
+ } else {
+ // Multiple ranges. Multipart reply and stuff needed.
+
+ array(string)|string content_type =
+ variant_heads["Content-Type"] || "application/octet-stream";
+
+ if(request_headers["request-range"]) {
+ // Compat with old Netscape.
+ variant_heads["Content-Type"] =
+ "multipart/x-byteranges; boundary=" BOUND;
+ } else {
+ variant_heads["Content-Type"] =
+ "multipart/byteranges; boundary=" BOUND;
+ }
+
+ if (objectp(file->file)) {
+ // We do this by replacing the file object with a wrapper.
+ // Nice and handy.
+ file->file = MultiRangeWrapper(file, heads, ranges,
+ content_type, this_object());
+ } else {
+ array(string) res = allocate(sizeof(ranges)*3+1);
+ mapping(string:string) part_heads = ([
+ "Content-Type":content_type,
+ ]);
+ int j;
+ foreach(ranges; int i; array(int) range) {
+ res[j++] = "\r\n--" BOUND "\r\n";
+ part_heads["Content-Range"] =
+ sprintf("bytes %d-%d/%d", @range, file->len);
+ res[j++] = Roxen.make_http_headers(part_heads);
+ res[j++] = data[range[0]..range[1]];
+ }
+ res[j++] = "\r\n--" BOUND "\r\n";
+ file->len = sizeof(file->data = res * "");
+ variant_heads["Content-Length"] = (string)file->len;
+ }
+ }
+ } else {
+ // Got the header, but the specified ranges were out of bounds.
+ // Reply with a 416 Requested Range not satisfiable.
+ file->error = 416;
+ variant_heads["Content-Range"] = "*/"+file->len;
+ file->file = file->data = file->type = file->len = 0;
+ }
+ }
+ }
+ }
+
+ variant_string = Roxen.make_http_headers(variant_heads);
+ full_headers = prot + " " + file->error + head_string + variant_string;
+ REQUEST_WERR(sprintf("HTTP: full_headers: %O\n"
+ "HTTP: file: %O\n",
+ full_headers, file));
+
low_send_result(full_headers, file->data, file->len, file->file);
}
- else
- {
- file->len = 1; // Keep those alive, please...
- low_send_result(full_headers, "");
+ else {
+ // RAW or HTTP/0.9 mode.
+ // No headers!
+
+ if(!file->type) file->type="text/plain";
+ low_send_result("", file->data, file->len, file->file);
}
-
+ MARK_FD("HTTP handled");
+
+
TIMER_END(send_result);
}
2444: Inside #if defined(RAM_CACHE)
Roxen.make_http_headers(([
"Date":Roxen.http_date(predef::time(1)),
"Content-Length":(string)len,
+ "Content-Type":file->type,
"Connection":misc->connection ||
([ "HTTP/1.1":"keep-alive" ])[prot] || "close",
]));