Branch: Tag:

2005-11-25

2005-11-25 16:14:42 by Henrik Grubbström (Grubba) <grubba@grubba.org>

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",    ]));