Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

supporting serving an index.html for directories #902

Merged
merged 1 commit into from
Feb 21, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions source/vibe/http/fileserver.d
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,18 @@ HTTPServerRequestDelegate serveStaticFiles(Path local_path, HTTPFileServerSettin
logDebug("path '%s' not starting with '%s'", srv_path, settings.serverPathPrefix);
return;
}

auto rel_path = srv_path[settings.serverPathPrefix.length .. $];
auto rpath = Path(rel_path);
logTrace("Processing '%s'", srv_path);

rpath.normalize();
logDebug("Path '%s' -> '%s'", rel_path, rpath.toNativeString());
if (rpath.empty) {
// TODO: support searching for an index file
return;
} else if (rpath.absolute) {
if (rpath.absolute) {
logDebug("Path is absolute, not responding");
return;
} else if (rpath[0] == "..") return; // don't respond to relative paths outside of the root path
} else if (!rpath.empty && rpath[0] == "..")
return; // don't respond to relative paths outside of the root path

sendFile(req, res, local_path ~ rpath, settings);
}
Expand Down Expand Up @@ -138,9 +136,9 @@ HTTPServerRequestDelegate serveStaticFile(string local_path, HTTPFileServerSetti
class HTTPFileServerSettings {
string serverPathPrefix = "/";
Duration maxAge;// = hours(24);
bool failIfNotFound = false;
HTTPFileServerOption options = HTTPFileServerOption.defaults; /// additional options
string[string] encodingFileExtension;

/**
Called just before headers and data are sent.
Allows headers to be customized, or other custom processing to be performed.
Expand All @@ -162,17 +160,43 @@ class HTTPFileServerSettings {
this();
serverPathPrefix = path_prefix;
}
}

deprecated("Use .options and HTTPFileServerOption.failIfNotFound instead.")
@property bool failIfNotFound() const { return options & HTTPFileServerOption.failIfNotFound; }

deprecated("Use .options and HTTPFileServerOption.failIfNotFound instead.")
@property void failIfNotFound(bool val) {
if (val)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options = (-int(val) ^ options) & HTTPFileServerOption.failIfNotFound;
Shorter and without branching.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a little too cryptic for too little benefit, IMO. In cases where not both is possible, I personally usually favor the clearer and more readable variant over the more concise one.

options |= HTTPFileServerOption.failIfNotFound;
else
options &= ~HTTPFileServerOption.failIfNotFound;
}
}


/**
Additional options for the static file server.
*/
enum HTTPFileServerOption {
none = 0,
/// respond with 404 if a file was not found
failIfNotFound = 1 << 0,
/// serve index.html for directories
serveIndexHTML = 1 << 1,
/// default options are serveIndexHTML
defaults = serveIndexHTML,
}


private void sendFile(HTTPServerRequest req, HTTPServerResponse res, Path path, HTTPFileServerSettings settings)
{
auto pathstr = path.toNativeString();

// return if the file does not exist
if( !existsFile(pathstr) ){
if( settings.failIfNotFound ) throw new HTTPStatusException(HTTPStatus.NotFound);
else return;
if (!existsFile(pathstr)){
if (settings.options & HTTPFileServerOption.failIfNotFound)
throw new HTTPStatusException(HTTPStatus.NotFound);
return;
}

FileInfo dirent;
Expand All @@ -182,15 +206,18 @@ private void sendFile(HTTPServerRequest req, HTTPServerResponse res, Path path,
}

if (dirent.isDirectory) {
if (settings.options & HTTPFileServerOption.serveIndexHTML)
return sendFile(req, res, path ~ "index.html", settings);
logDebugV("Hit directory when serving files, ignoring: %s", pathstr);
if( settings.failIfNotFound ) throw new HTTPStatusException(HTTPStatus.NotFound);
else return;
if (settings.options & HTTPFileServerOption.failIfNotFound)
throw new HTTPStatusException(HTTPStatus.NotFound);
return;
}

auto lastModified = toRFC822DateTimeString(dirent.timeModified.toUTC());
// simple etag generation
auto etag = "\"" ~ hexDigest!MD5(pathstr ~ ":" ~ lastModified ~ ":" ~ to!string(dirent.size)).idup ~ "\"";

res.headers["Last-Modified"] = lastModified;
res.headers["Etag"] = etag;
if (settings.maxAge > seconds(0)) {
Expand Down Expand Up @@ -221,7 +248,7 @@ private void sendFile(HTTPServerRequest req, HTTPServerResponse res, Path path,
res.headers.remove("Content-Encoding");
res.headers["Content-Type"] = mimetype;
res.headers["Content-Length"] = to!string(dirent.size);

// check for already encoded file if configured
string encodedFilepath;
auto pce = "Content-Encoding" in res.headers;
Expand Down Expand Up @@ -251,18 +278,18 @@ private void sendFile(HTTPServerRequest req, HTTPServerResponse res, Path path,
encodedFilepath = null;
}
}

if(settings.preWriteCallback)
settings.preWriteCallback(req, res, pathstr);

// for HEAD responses, stop here
if( res.isHeadResponse() ){
res.writeVoidBody();
assert(res.headerWritten);
logDebug("sent file header %d, %s!", dirent.size, res.headers["Content-Type"]);
return;
}

// else write out the file contents
//logTrace("Open file '%s' -> '%s'", srv_path, pathstr);
FileStream fil;
Expand Down