2004-05-24
2004-05-24 13:40:50 by Stephen R. van den Berg <srb@cuci.nl>
-
36160e3f1def67437799df0852908b57b54b2695
(785 lines)
(+412/-373)
[
Show
| Annotate
]
Branch: Aphoto_2.0_beta3
Sync with webserver for http.pike -r1.449
Other files were collateral damage :-)
Rev: server/data/include/config.h:1.34
Rev: server/plugins/protocols/http.pike:1.399
Rev: server/server_core/loader.pike:1.383
Rev: server/server_core/prototypes.pike:1.71
2:
// Modified by Francesco Chemolli to add throttling capabilities.
// Copyright © 1996 - 2001, Roxen IS.
- constant cvs_version = "$Id: http.pike,v 1.398 2004/05/23 02:35:26 _cvs_stephen Exp $";
+ constant cvs_version = "$Id: http.pike,v 1.399 2004/05/24 13:40:49 _cvs_stephen Exp $";
// #define REQUEST_DEBUG
#define MAGIC_ERROR
15: Inside #if defined(PROFILE)
#ifdef PROFILE
#define HRTIME() gethrtime()
- #define HRSEC(X) ((int)((X)*1000000))
- #define SECHR(X) ((X)/(float)1000000)
+
int req_time = HRTIME();
#endif
26: Inside #if defined(REQUEST_DEBUG)
#ifdef REQUEST_DEBUG
int footime, bartime;
- #define REQUEST_WERR(X) bartime = gethrtime()-footime; werror("%s (%d)\n", (X), bartime);footime=gethrtime()
+ #define REQUEST_WERR(X) do {bartime = gethrtime()-footime; werror("%s (%d)\n", (X), bartime);footime=gethrtime();} while (0)
#else
- #define REQUEST_WERR(X)
+ #define REQUEST_WERR(X) do {} while (0)
#endif
#ifdef FD_DEBUG
- #define MARK_FD(X) \
- catch{ \
- REQUEST_WERR("FD " + my_fd->query_fd() + ": " + (X)); \
- mark_fd(my_fd->query_fd(), (X)+" "+remoteaddr); \
- }
+ #define MARK_FD(X) do { \
+ int _fd = my_fd && my_fd->query_fd ? my_fd->query_fd() : -1; \
+ REQUEST_WERR("FD " + (_fd == -1 ? sprintf ("%O", my_fd) : _fd) + ": " + (X)); \
+ mark_fd(_fd, (X)+" "+remoteaddr); \
+ } while (0)
#else
- #define MARK_FD(X)
+ #define MARK_FD(X) do {} while (0)
#endif
#ifdef THROTTLING_DEBUG
57:
private static int wanted_data, have_data;
private static String.Buffer data_buffer;
+ private static multiset(string) none_match;
+
+ int kept_alive;
+
+ #ifdef DEBUG
+ #define CHECK_FD_SAFE_USE do { \
+ if (this_thread() != roxen->backend_thread && \
+ (my_fd->query_read_callback() || my_fd->query_write_callback() || \
+ my_fd->query_close_callback() || \
+ !zero_type (find_call_out (do_timeout)))) \
+ error ("Got callbacks but not called from backend thread.\n"); \
+ } while (0)
+ #else
+ #define CHECK_FD_SAFE_USE do {} while (0)
+ #endif
+
#include <roxen.h>
#include <module.h>
#include <variables.h>
82:
}
#endif // REQUEST_DEBUG
]);
+ mapping (string:mixed) connection_misc = ([ ]);
mapping (string:string) cookies = ([ ]);
mapping (string:string) request_headers = ([ ]);
mapping (string:string) client_var = ([ ]);
105:
switch( i )
{
case 0:
- return conf->authenticate( this );
+ return conf->authenticate( this_object() );
case 1:
- if( u = conf->authenticate( this ) )
+ if( u = conf->authenticate( this_object() ) )
return u->name();
if( realauth )
return (realauth/":")[0];
case 2:
- if( u = conf->authenticate( this ) )
+ if( u = conf->authenticate( this_object() ) )
return 0;
if( realauth )
return ((realauth/":")[1..])*":";
125:
}
}
- AuthEmulator auth;
+ array|AuthEmulator auth;
- string charset_name( function|string what )
- {
- switch( f )
- {
- case string_to_unicode: return "ISO10646-1";
- case string_to_utf8: return "UTF-8";
- default: return upper_case((string)what);
- }
- }
-
- function charset_function( function|string what, int allow_entities )
- {
- switch( what )
- {
- case "ISO-10646-1":
- case "ISO10646-1":
- case string_to_unicode:
- return string_to_unicode;
- case "UTF-8":
- case string_to_utf8:
- return string_to_utf8;
- default:
- catch {
- // If current file is "text/html" or "text/xml" we'll use an entity
- // encoding fallback instead of empty string subsitution.
- function fallback_func =
- allow_entities &&
- (file->type[0..8] == "text/html" || file->type[0..7] == "text/xml") &&
- lambda(string char) {
- return sprintf("&#x%x;", char[0]);
- };
- return
- Roxen._charset_decoder( Locale.Charset.encoder( (string) what,
- "", fallback_func ) )
- ->decode;
- };
- }
- return lambda(string what){return what;};
- }
-
- static array(string) join_charset( string old,
- function|string add,
- function oldcodec,
- int allow_entities )
- {
- switch( old&&upper_case(old) )
- {
- case 0:
- return ({ charset_name( add ), charset_function( add, allow_entities ) });
- case "ISO10646-1":
- case "UTF-8":
- return ({ old, oldcodec }); // Everything goes here. :-)
- case "ISO-2022":
- return ({ old, oldcodec }); // Not really true, but how to know this?
- default:
- // Not true, but there is no easy way to add charsets yet...
- return ({ charset_name( add ), charset_function( add, allow_entities ) });
- }
- }
-
- static array(string) output_encode( string what, int|void allow_entities,
- string|void force_charset )
- {
- if( !force_charset )
- {
- string charset;
- function encoder;
-
- foreach( output_charset, string|function f )
- [charset,encoder] = join_charset( charset, f, encoder, allow_entities );
-
-
- if( !encoder )
- if( String.width( what ) > 8 )
- {
- charset = "UTF-8";
- encoder = string_to_utf8;
- }
- if( encoder )
- what = encoder( what );
- return ({ charset, what });
- }
- else
- return ({
- 0,
- Locale.Charset.encoder( (force_charset/"=")[-1] )->feed( what )->drain()
- });
- }
-
+
void decode_map( mapping what, function decoder )
{
foreach( what; mixed q; mixed val )
247:
void decode_charset_encoding( string|function(string:string) decoder )
{
+ if( misc->request_charset_decoded )
+ return;
if(stringp(decoder))
decoder = Roxen._charset_decoder(Locale.Charset.decoder(decoder))->decode;
-
- if( misc->request_charset_decoded )
+ if( !decoder )
return;
misc->request_charset_decoded = 1;
- if( !decoder )
- return;
-
+
string safe_decoder(string s) {
catch { return decoder(s); };
return s;
384: Inside #if defined(OLD_RXML_CONFIG)
};
void do_send_reply( string what, string url ) {
+ CHECK_FD_SAFE_USE;
url = url_base() + url[1..];
my_fd->set_blocking();
my_fd->write( prot + " 302 ChiliMoon config coming up\r\n"+
415:
private static mixed f, line;
private static int hstart;
- class CacheKey {
- #if ID_CACHEKEY_DEBUG
- constant __num = ({ 0 });
- int _num;
- string _sprintf(int t) {
- return t=='O' && sprintf("%O(#%d)", this_program, _num);
- }
- void create() { _num = ++__num[0]; }
- void destroy() { werror("CacheKey(#" + _num + "): --DESTROY--\n"
- "%s\n\n", "" || describe_backtrace(backtrace())); }
- #endif
- }
-
+
//! Parse a cookie string.
//!
//! @param contents
440:
if(!contents)
return cookies;
+ // misc->cookies += ({contents});
foreach(((contents/";") - ({""})), string c)
{
string name, value;
621:
line = res[1];
request_headers = res[2];
}
+ TIMER_END(parse_got);
return parse_got_2();
}
private final int parse_got_2( )
{
-
+ TIMER_START(parse_got_2);
+ TIMER_START(parse_got_2_parse_line);
string trailer, trailer_trailer;
multiset (string) sup;
string a, b, s="", linename, contents;
667:
else
{
my_fd->write("PONG\r\n");
+ TIMER_END(parse_got_2);
return 2;
}
s = data = ""; // no headers or extra data...
678:
/* Not reached */
break;
}
+ TIMER_END(parse_got_2_parse_line);
REQUEST_WERR(sprintf("HTTP: request line %O", line));
REQUEST_WERR(sprintf("HTTP: headers %O", request_headers));
REQUEST_WERR(sprintf("HTTP: data (length %d) %O", strlen(data),data));
695:
}
if(!remoteaddr) {
REQUEST_WERR("HTTP: No remote address.");
- TIMER_END(parse_got);
+ TIMER_END(parse_got_2);
return 2;
}
}
-
+ TIMER_START(parse_got_2_parse_headers);
foreach( (array)request_headers, [string linename, array|string contents] )
{
if( arrayp(contents) ) contents = contents[0];
712:
case "authorization": rawauth = contents; break;
case "referer": referer = ({contents}); break;
case "if-modified-since": since=contents; break;
+ case "if-match": break; // Not supported yet.
+ case "if-none-match":
+ none_match = (multiset)((contents-" ")/",");
+ break;
case "proxy-authorization":
array y;
748:
misc->range = contents[6..];
break;
-
- case "host":
+
case "connection":
-
+ misc->client_connection = (<@(lower_case(contents)/" " - ({""}))>);
+ if (misc->client_connection->close) {
+ misc->connection = "close";
+ } else if (misc->client_connection["keep-alive"]) {
+ misc->connection = "keep-alive";
+ }
+ break;
+ case "host":
misc[linename] = lower_case(contents);
break;
case "content-type":
misc[linename] = contents;
break;
-
+ case "destination":
+ misc["new-uri"] = contents;
+ if (mixed err = catch {
+ misc["new-uri"] = Standards.URI(contents)->path;
+ }) {
+ #ifdef DEBUG
+ report_debug(sprintf("Destination header contained a bad URI: %O\n"
+ "%s", contents, describe_error(err)));
+ #endif /* DEBUG */
}
-
+ break;
}
-
+ }
+ TIMER_END(parse_got_2_parse_headers);
+ TIMER_START(parse_got_2_more_data);
if(misc->len)
{
if(!data) data="";
768:
if(strlen(data) < l)
{
REQUEST_WERR(sprintf("HTTP: More data needed in %s.", method));
- TIMER_END(parse_got);
+ ready_to_receive();
+ TIMER_END(parse_got_2);
return 0;
}
leftovers = data[l+2..];
data = data[..l+1];
- if (method == "POST") {
+ switch(method) {
+ case "POST":
switch(lower_case((((misc["content-type"]||"")+";")/";")[0]-" "))
{
default:
831:
}
}
}
+ TIMER_END(parse_got_2_more_data);
if (!(< "HTTP/1.0", "HTTP/0.9" >)[prot]) {
if (!misc->host) {
// RFC 2616 requires this behaviour.
840:
"Content-Length: 0\r\n"
"Date: "+Roxen.http_date(predef::time())+"\r\n"
"\r\n");
+ TIMER_END(parse_got_2);
return 2;
}
}
- TIMER_END(parse_got);
+ TIMER_END(parse_got_2);
return 3; // Done.
}
863:
{
file = 0;
conf && conf->connection_drop( this );
- #ifdef REQUEST_DEBUG
- if (my_fd)
+ if (my_fd) {
MARK_FD("HTTP my_fd in HTTP disconnected?");
- #endif
+ my_fd->close();
+ my_fd = 0;
+ }
MERGE_TIMERS(conf);
if(do_not_disconnect) return;
destruct();
}
- void end(int|void keepit)
+ static void cleanup_request_object()
{
if( conf )
conf->connection_drop( this );
-
+ }
+
+ void end(int|void keepit)
+ {
+ CHECK_FD_SAFE_USE;
+
+ cleanup_request_object();
+
if(keepit
&& !file->raw
- && (misc->connection == "keep-alive" ||
- (prot == "HTTP/1.1" && misc->connection != "close"))
+ && misc->connection != "close"
+ && ((prot == "HTTP/1.1") || (misc->connection == "keep-alive"))
&& my_fd
-
+ // Is this necessary now when this function no longer is called
+ // from the close callback? /mast
&& !catch(my_fd->query_address()) )
{
// Now.. Transfer control to a new http-object. Reset all variables etc..
- this_program o = this_program(0, 0, 0);
+ object o = object_program(this)(0, 0, 0);
o->remoteaddr = remoteaddr;
o->client = client;
o->supports = supports;
892:
o->host = host;
o->conf = conf;
o->pipe = pipe;
- MARK_FD("HTTP kept alive");
+ o->connection_misc = connection_misc;
+ o->kept_alive = kept_alive+1;
object fd = my_fd;
my_fd=0;
o->chain(fd,port_obj,leftovers);
901:
return;
}
+ data_buffer = 0;
pipe = 0;
- if(objectp(my_fd))
- {
- MARK_FD("HTTP closed");
- mixed err = catch
- {
- // Don't set to blocking mode if SSL.
- if (!my_fd->CipherSpec) {
- my_fd->set_blocking();
- }
- my_fd->close();
- destruct(my_fd);
- };
- #ifdef DEBUG
- if (err) report_debug ("Close failure (1): %s", describe_backtrace (err));
- #endif
- err = catch {
- my_fd = 0;
- };
- #ifdef DEBUG
- if (err) report_debug ("Close failure (2): %s", describe_backtrace (err));
- #endif
- }
+
disconnect();
}
936:
MARK_FD("HTTP timeout");
end();
} else {
+ #ifdef DEBUG
+ error ("This shouldn't happen.\n");
+ #endif
// premature call_out... *¤#!"
call_out(do_timeout, 10);
MARK_FD("HTTP premature timeout");
951:
(fun ? "&fun="+Roxen.http_encode_url(fun) : "") +
"&off="+qq+
"&error="+eid+
+ "&error_md5="+get_err_md5(get_err_info(eid))+
(line ? "&line="+line+"#here" : "") +
"\">");
}
969:
";
}
- string format_backtrace(int eid)
+ static string get_err_md5(array(string|array(string)|array(array)) err_info)
{
- array info = cache_lookup( "http_bt_error", eid);
- if(!info)
- return error_page_header("Unregistered Error");
- [string msg, array(string) rxml_bt, array(array) bt,
- string raw_bt_descr, string raw_url, string raw] = info;
+ if (err_info) {
+ return String.string2hex(Crypto.MD5.hash(err_info[3]));
+ }
+ return "NONE";
+ }
-
+ static array(string|array(string)|array(array)) get_err_info(int eid,
+ string|void md5)
+ {
+ array(string|array(string)|array(array)) err_info =
+ core.query_var ("errors")[eid];
-
+ if (!err_info ||
+ (md5 && (md5 != get_err_md5(err_info)))) {
+ // Extra safety...
+ return 0;
+ }
+ return err_info;
+ }
+
+
+ string format_backtrace(int eid, string|void md5)
+ {
+ array(string|array(string)|array(array)) err_info = get_err_info(eid, md5);
+
+ if (!err_info) {
+ return error_page_header("Unregistred Error");
+ }
+
+ [string msg, array(string) rxml_bt, array(array) bt,
+ string raw_bt_descr, string raw_url, string raw] = err_info;
+
string res = error_page_header ("Internal Server Error") +
"<h1>" + replace (Roxen.html_encode_string (msg), "\n", "<br />\n") + "</h1>\n";
1008:
res += "</ul>\n\n";
}
- res += ("<p><b><a href=\"/(old_error,plain)/error/?error="+eid+"\">"
+ res += ("<p><b><a href=\"/(old_error,plain)/error/?"
+ "error="+eid+
+ "&error_md5="+get_err_md5(get_err_info(eid))+
+ "\">"
"Generate text only version of this error message, for bug reports"+
"</a></b></p>\n\n");
return res+"</body></html>";
1040:
{
mixed err = _err;
_err = 0; // hide in backtrace, they are bad enough anyway...
+ mapping e = core.query_var("errors");
+ if(!e) core.set_var("errors", ([]));
+ e = core.query_var("errors"); /* threads... */
- int id;
- do {
- id = random( (1<<64)-1 ); // 64 bit random number
- } while(!cache_lookup("http_bt_error", id));
+ int id = ++e[0];
+ if(id>1024) id = 1;
string msg;
array(string) rxml_bt;
1112:
}
add_cvs_ids (err);
- cache_set( "http_bt_error", id, ({msg,rxml_bt,bt,describe_backtrace (err),raw_url,censor(raw)}) );
+ e[id] = ({msg,rxml_bt,bt,describe_backtrace (err),raw_url,censor(raw)});
return id;
}
- array get_error(string eid)
+ array get_error(string eid, string md5)
{
- return cache_lookup( "http_bt_error", (int)eid );
+ mapping e = core.query_var("errors");
+ if(e) {
+ array r = e[(int)eid];
+ if (r && md5 == String.string2hex(Crypto.MD5.hash(r[3]))) {
+ return r;
}
-
+ }
+ return 0;
+ }
void internal_error(array _err)
1153:
}
// This macro ensures that something gets reported even when the very
- // call to internal_error() fails. That happens eg when this has been
+ // call to internal_error() fails. That happens eg when "this" has been
// destructed.
#define INTERNAL_ERROR(err) do { \
if (mixed __eRr = catch (internal_error (err))) \
- report_error("Internal server error: " + describe_backtrace(err) + \
- "internal_error() also failed: "+describe_backtrace(__eRr));\
- } while(0)
+ report_error("Internal server error: " + describe_backtrace(err) + \
+ "internal_error() also failed: " + describe_backtrace(__eRr)); \
+ } while (0)
int wants_more()
{
1185:
MERGE_TIMERS(conf);
if( conf )
conf->connection_drop( this );
- mixed err = catch // paranoia
- {
- my_fd->close();
- destruct( my_fd );
- destruct( );
- };
- #ifdef DEBUG
- if (err) report_debug ("Close failure (3): %s", describe_backtrace (err));
- #endif
+ call_out (disconnect, 0);
return;
}
TIMER_END(do_log);
1421:
// Tell the client that it can start sending some more data
void ready_to_receive()
{
- if (clientprot == "HTTP/1.1" && request_headers->Expect &&
- (request_headers->Expect == "100-continue" ||
- has_value(request_headers->Expect, "100-continue" )))
+ // FIXME: Only send once?
+ if (clientprot == "HTTP/1.1" && request_headers->expect &&
+ (request_headers->expect == "100-continue" ||
+ has_value(request_headers->expect, "100-continue" )))
my_fd->write("HTTP/1.1 100 Continue\r\n");
}
1432:
{
TIMER_START(send_result);
+ CHECK_FD_SAFE_USE;
+
array err;
int tmp;
- mapping heads;
+
string head_string="";
if (result)
file = result;
#ifdef PROFILE
- float elapsed = SECHR(HRTIME()-req_time);
+ int elapsed = HRTIME()-req_time;
string nid =
#ifdef FILE_PROFILE
(raw_url/"?")[0]
#else
dirname((raw_url/"?")[0])
#endif
- ;
+ + "?method="+method;
array p;
- if(!(p=conf->profile_map[nid]))
- p = conf->profile_map[nid] = ({0,0.0,0.0});
+ if(!(p=conf->profile_map[nid])) {
+ // ({ count, sum, max })
+ p = conf->profile_map[nid] = ({0, 0, 0});
+ }
p[0]++;
p[1] += elapsed;
if(elapsed > p[2]) p[2]=elapsed;
#endif
- REQUEST_WERR(sprintf("HTTP: response: prot %O, method %O, file %O, misc: %O",
- prot, method, file, misc));
+ #ifdef DEBUG_CACHEABLE
+ report_debug("<=== Request for %s returned cacheable %d (proto cache %s).\n",
+ raw_url, misc->cacheable,
+ misc->no_proto_cache ? "disabled" : "enabled");
+ #endif
if( prot == "HTTP/0.9" ) misc->no_proto_cache = 1;
1467:
{
misc->no_proto_cache = 1;
if(misc->error_code)
- file = Roxen.http_low_answer(misc->error_code, errors[misc->error]);
+ file = Roxen.http_status(misc->error_code, errors[misc->error]);
else if(err = catch {
file = conf->error_file( this );
})
1487:
}
if(file->type == "raw") file->raw = 1;
- else if(!file->type) file->type="text/plain";
+
}
- if(!file->raw)
+ if(!file->raw && (prot != "HTTP/0.9"))
{
- heads = ([]);
- if (!file->stat) file->stat = misc->stat;
- if(objectp(file->file)) {
- if(!file->stat)
- file->stat = file->file->stat();
- if (zero_type(misc->cacheable) && file->file->is_file) {
- // Assume a cacheablity on the order of the age of the file.
- misc->cacheable = (predef::time(1) - file->stat[ST_MTIME])/4;
+ 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 */
}
- }
+
- if( Stat fstat = file->stat )
- {
- if( !file->len )
- file->len = fstat[1];
-
- if ( fstat[ST_MTIME] > misc->last_modified )
- misc->last_modified = fstat[ST_MTIME];
+ string head_status = file->rettext;
+ if (head_status) {
+ if (!file->file && !file->data &&
+ (!file->type || file->type == "text/html")) {
+ // If we got no body then put the message there to make it
+ // more visible.
+ file->data = "<html><body>" +
+ replace (Roxen.html_encode_string (head_status), "\n", "<br />\n") +
+ "</body></html>";
+ file->len = sizeof (file->data);
+ file->type = "text/html";
}
-
+ if (has_value (head_status, "\n"))
+ // Fold lines nicely.
+ head_status = map (head_status / "\n", String.trim_all_whites) * " ";
+ }
- if( !zero_type(misc->cacheable) &&
- misc->cacheable < INITIAL_CACHEABLE ) {
- if (misc->cacheable == 0) {
- heads["Expires"] = Roxen.http_date( 0 );
+ mapping(string:string) heads = make_response_headers (file);
- if (misc->cacheable < INITIAL_CACHEABLE) {
- // Data with expiry is assumed to have been generated at
- // the same instant.
- misc->last_modified = predef::time(1);
+ if (file->error == 200) {
+ int conditional;
+ if (none_match) {
+ // NOTE: misc->etag may be zero below, but that's ok.
+ if (none_match[misc->etag] || (misc->etag && none_match["*"])) {
+ // We have a if-none-match header that matches our etag.
+ if ((<"HEAD", "GET">)[method]) {
+ // RFC 2616 14.26:
+ // Instead, if the request method was GET or HEAD, the server
+ // SHOULD respond with a 304 (Not Modified) response, including
+ // the cache- related header fields (particularly ETag) of one
+ // of the entities that matched. For all other request methods,
+ // the server MUST respond with a status of 412 (Precondition
+ // Failed).
+ conditional = 304;
+ } else {
+ conditional = 412;
}
-
+ } else {
+ conditional = -1;
}
- else
- heads["Expires"] = Roxen.http_date( predef::time(1)+misc->cacheable );
+
}
-
- if (misc->last_modified)
- heads["Last-Modified"] = Roxen.http_date(misc->last_modified);
-
- if(since && (!file->error || file->error == 200) && misc->last_modified)
+ if(since && misc->last_modified && (conditional >= 0))
{
- // ({ time, len })
+ /* ({ time, len }) */
array(int) since_info = Roxen.parse_since( since );
-
- if ( (since_info[0] >= misc->last_modified) &&
- ((since_info[1] == -1) || (since_info[1] == file->len))
+ // 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) )
+ (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.
+ )
{
- file->error = 304;
- file->file = 0;
- file->data="";
+ conditional = conditional || 304;
+ } else {
+ conditional = -1;
}
}
-
- if(prot != "HTTP/0.9")
- {
- string h, charset="";
-
- if( stringp(file->data) )
- {
- if (file["type"][0..4] == "text/")
- {
- [charset,file->data] = output_encode( file->data, 1 );
- if( charset && has_value(file["type"], "; charset=") )
- charset = "; charset="+charset;
- else
- charset = "";
- }
- file->len = strlen(file->data);
- }
- heads["Content-Type"] = file["type"]+charset;
- heads["Accept-Ranges"] = "bytes";
- heads["Server"] = replace(version(), " ", "·");
- if( misc->connection )
- heads["Connection"] = misc->connection;
-
- if(file->encoding) heads["Content-Encoding"] = file->encoding;
-
- if(!file->error)
- file->error=200;
-
- heads->Date = Roxen.http_date(predef::time(1));
- if(file->expires)
- heads->Expires = Roxen.http_date(file->expires);
-
- if(mappingp(file->extra_heads))
- heads |= file->extra_heads;
-
- if(mappingp(misc->moreheads))
- heads |= misc->moreheads;
-
- if(misc->range && file->len && objectp(file->file) && !file->data &&
- file->error == 200 && (method == "GET" || method == "HEAD"))
+ if (conditional > 0) {
+ // 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
1605:
{
heads["Content-Range"] = sprintf("bytes %d-%d/%d",
@ranges[0], file->len);
- file->start = ranges[0][0];
+ 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.
1614:
file->file = MultiRangeWrapper(file, heads, ranges, this);
}
} else {
- // Got the header, but the specified ranges was out of bounds.
+ // 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->data = "The requested byte range is out-of-bounds. Sorry.";
- file->len = strlen(file->data);
- file->file = 0;
+ file->file = file->data = file->type = file->len = 0;
}
}
}
}
- head_string = sprintf("%s %d %s\r\n",
- prot, file->error,
- file->rettext ||errors[file->error]||"");
+ }
- // if( file->len > 0 || (file->error != 200) )
+ head_string = sprintf("%s %d %s\r\n", prot, file->error,
+ head_status || errors[file->error] || "");
+
+ // Must update the content length after the modifications of the
+ // data to send that might have been done above for 206 or 304.
heads["Content-Length"] = (string)file->len;
- // Some browsers, e.g. Netscape 4.7, doesn't trust a zero
+ // Some browsers, e.g. Netscape 4.7, don't trust a zero
// content length when using keep-alive. So let's force a
// close in that case.
if( file->error/100 == 2 && file->len <= 0 )
1641:
heads->Connection = "close";
misc->connection = "close";
}
+
if( mixed err = catch( head_string += Roxen.make_http_headers( heads ) ) )
{
#ifdef DEBUG
- report_debug("Roxen.make_http_headers failed: " +
- describe_error(err));
+ report_debug ("Roxen.make_http_headers failed: " +
+ describe_error (err));
#endif
- foreach( heads; string x; mixed head )
- if( stringp( head ) )
- head_string += x+": "+head+"\r\n";
- else if( arrayp( head ) )
- foreach( head, string xx )
+ foreach(heads; string x; string|array(string) val) {
+ if (stringp(val))
+ head_string += x+": "+val+"\r\n";
+ else if( arrayp( val ) )
+ foreach( val, string xx )
head_string += x+": "+xx+"\r\n";
else if( catch {
- head_string += x+": "+(string)head;
+ head_string += x+": "+(string)val+"\r\n";
} )
error("Illegal value in headers array! "
"Expected string or array(string)\n");
-
+ }
head_string += "\r\n";
}
- if( strlen( charset ) || String.width( head_string ) > 8 )
+ if (sscanf (heads["Content-Type"], "; charset=%s", string charset) ||
+ String.width( head_string ) > 8 )
head_string = output_encode( head_string, 0, charset )[1];
conf->hsent += strlen(head_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!=304) )
+ if( (method!="HEAD") && (file->error!=204) )
// No data for these two...
{
#ifdef RAM_CACHE
1684: Inside #if defined(RAM_CACHE)
if( file->file ) data += file->file->read();
if( file->data ) data += file->data;
MY_TRACE_ENTER (sprintf ("Storing in ram cache, entry: %O", raw_url), 0);
+ MY_TRACE_LEAVE ("");
conf->datacache->set( raw_url, data,
([
// We have to handle the date header.
"hs":head_string,
"key":misc->cachekey,
-
+ "etag":misc->etag,
"callbacks":misc->_cachecallbacks,
"len":file->len,
// fix non-keep-alive when sending from cache
1699: Inside #if defined(RAM_CACHE)
]),
misc->cacheable );
file = ([ "data":data, "raw":file->raw, "len":strlen(data) ]);
- MY_TRACE_LEAVE ("");
+
}
}
#endif
- if(strlen(head_string))
- send(head_string);
- if(file->data && strlen(file->data))
- send(file->data, file->len, file->start);
- if(file->file)
- send(file->file, file->len, file->start);
+ if(!kept_alive &&
+ (file->len > 0) &&
+ ((sizeof(head_string) + file->len) < (HTTP_BLOCKING_SIZE_THRESHOLD)))
+ {
+ // The first time we get a request, the output buffers will
+ // be empty. We can thus just do a single blocking write()
+ // if the data will fit in the output buffer (usually 4KB).
+ int s;
+ TIMER_END(send_result);
+ TIMER_START(blocking_write);
+ string data = head_string;
+ if (file->data)
+ data += file->data[..file->len-1];
+ if (file->file)
+ data += file->file->read(file->len);
+ #ifdef CONNECTION_DEBUG
+ werror ("HTTP: Response =================================================\n"
+ "%s\n",
+ replace (sprintf ("%O", data),
+ ({"\\r\\n", "\\n", "\\t"}),
+ ({"\n", "\n", "\t"})));
+ #else
+ REQUEST_WERR (sprintf ("HTTP: Send blocking %O", data));
+ #endif
+ s = my_fd->write(data);
+ TIMER_END(blocking_write);
+ return;
}
-
+ if(strlen(head_string)) send(head_string);
+ if(file->data && strlen(file->data)) send(file->data, file->len);
+ if(file->file) send(file->file, file->len);
+ }
else
{
-
+ if( strlen( head_string ) < (HTTP_BLOCKING_SIZE_THRESHOLD))
+ {
+ #ifdef CONNECTION_DEBUG
+ werror ("HTTP: Response =================================================\n"
+ "%s\n",
+ replace (sprintf ("%O", head_string),
+ ({"\\r\\n", "\\n", "\\t"}),
+ ({"\n", "\n", "\t"})));
+ #else
+ REQUEST_WERR (sprintf ("HTTP: Send headers blocking %O", head_string));
+ #endif
+ return;
+ }
send(head_string);
file->len = 1; // Keep those alive, please...
}
1719:
start_sender();
}
-
+
// Execute the request
void handle_request( )
{
1728: Inside #if defined(MAGIC_ERROR)
#ifdef MAGIC_ERROR
if(prestate->old_error)
{
- array err = get_error(variables->error);
+ array err = get_error(variables->error, variables->error_md5 || "NONE");
if(err && arrayp(err))
{
if(prestate->plain)
1774:
file = result;
}
- if( file )
- if( file->try_again_later )
+ if( file && file->try_again_later )
{
if( objectp( file->try_again_later ) )
;
1812:
// Then use the port object.
else {
- string host = port_obj->conf_data[conf]->hostname;
+ string host = (port_obj->conf_data[conf] ||
+ (["hostname":"*"]))->hostname;
if (host == "*")
if (conf && sizeof (host = conf->get_url()) &&
sscanf (host, "%*s://%[^:/]", host) == 2) {
1839:
// array ccd = ({});
void got_data(mixed fooid, string s)
{
+ #ifdef CONNECTION_DEBUG
+ werror ("HTTP: Request --------------------------------------------------\n"
+ "%s\n",
+ replace (sprintf ("%O", s),
+ ({"\\r\\n", "\\n", "\\t"}),
+ ({"\n", "\n", "\t"})));
+ #else
REQUEST_WERR(sprintf("HTTP: Got %O", s));
-
+ #endif
if(wanted_data)
{
-
+ // NOTE: No need to make a data buffer if it's a small request.
if(strlen(s) + have_data < wanted_data)
{
if(!data_buffer) {
1880:
{
if( conf )
conf->connection_drop( this );
- mixed err = catch // paranoia
- {
- my_fd->set_blocking();
- my_fd->close();
- destruct( my_fd );
- destruct( );
- };
- #ifdef DEBUG
- if (err) report_debug ("Close failure (4): %s", describe_backtrace (err));
- #endif
+ MARK_FD ("HTTP: Port closed.");
+ call_out (disconnect, 0);
return;
}
1914:
return;
}
- if( (< "GET", "HEAD" >)[method] )
+ #ifdef CONNECTION_DEBUG
+ werror ("HTTP: Request received -----------------------------------------\n");
+ #endif
+
+ if( method == "GET" || method == "HEAD" ) {
misc->cacheable = INITIAL_CACHEABLE; // FIXME: Make configurable.
-
+ #ifdef DEBUG_CACHEABLE
+ report_debug("===> Request for %s initiated cacheable to %d.\n", raw_url,
+ misc->cacheable);
+ #endif
+ }
TIMER_START(find_conf);
string path;
1995:
conf->received += strlen(raw);
conf->requests++;
}
+ CHECK_FD_SAFE_USE;
my_fd->set_close_callback(0);
my_fd->set_read_callback(0);
if (my_fd->set_accept_callback) my_fd->set_accept_callback(0);
2073: Inside #if defined(RAM_CACHE)
MY_TRACE_LEAVE ("Using entry from ram cache");
conf->hsent += strlen(file->hs);
cache_status["protcache"] = 1;
+ if( strlen( d ) < (HTTP_BLOCKING_SIZE_THRESHOLD) )
+ {
TIMER_END(cache_lookup);
-
+ }
+ else
+ {
+ TIMER_END(cache_lookup);
send( fix_date(file->hs)+d );
start_sender( );
-
+ }
return;
}
#ifndef RAM_CACHE_ASUME_STATIC_CONTENT
2094:
TIMER_START(parse_request);
if( things_to_do_when_not_sending_from_cache( ) )
return;
+ REQUEST_WERR(sprintf("HTTP: cooked headers %O", request_headers));
+ REQUEST_WERR(sprintf("HTTP: cooked variables %O", real_variables));
+ REQUEST_WERR(sprintf("HTTP: cooked cookies %O", cookies));
TIMER_END(parse_request);
- #ifdef THREADS
+
REQUEST_WERR("HTTP: Calling core.handle().");
core.handle(handle_request);
- #else
- handle_request();
- #endif
+
})
{
report_error("Internal server error: " + describe_backtrace(err));
my_fd->set_blocking();
my_fd->close();
-
+ my_fd = 0;
disconnect();
}
}
2114:
/* Get a somewhat identical copy of this object, used when doing
* 'simulated' requests. */
- this_program clone_me()
+ object clone_me()
{
- this_program c=this_program(0, port_obj, conf);
+ object c=object_program(this)(0, port_obj, conf);
#ifdef ID_OBJ_DEBUG
werror ("clone %O -> %O\n", this, c);
#endif
2145:
c->pragma = pragma;
c->cookies = cookies;
- c->request_headers = request_headers;
+ c->request_headers = request_headers + ([]);
c->my_fd = 0;
c->prot = prot;
c->clientprot = clientprot;
2180:
{
f->set_nonblocking(got_data, f->query_write_callback(), end);
my_fd = f;
+ CHECK_FD_SAFE_USE;
MARK_FD("HTTP connection");
if( c ) port_obj = c;
if( cc ) conf = cc;
2199:
MARK_FD("HTTP kept alive");
time = predef::time();
- if ( strlen( le ) )
+ if ( le && strlen( le ) )
got_data( 0,le );
else
{
2220:
{
if(do_not_disconnect == -1)
do_not_disconnect = 0;
- if(!processed)
- f->set_nonblocking(got_data, f->query_write_callback(), end);
+ f->set_nonblocking(!processed && got_data, f->query_write_callback(), end);
}
}