webserver: improved file caching #1366

Merged
merged 6 commits into from Sep 7, 2012
View
@@ -29,6 +29,9 @@
#define SECONDS_PER_MINUTE 60UL
#define SECONDS_TO_FILETIME 10000000UL
+static const char *DAY_NAMES[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+static const char *MONTH_NAMES[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
/////////////////////////////////////////////////
//
// CDateTimeSpan
@@ -994,6 +997,38 @@ void CDateTime::SetFromDBTime(const CStdString &time)
SetTime(hour, minute, second);
}
+void CDateTime::SetFromRFC1123DateTime(const CStdString &dateTime)
+{
+ CStdString date = dateTime;
+ date.Trim();
+
+ if (date.size() != 29)
+ return;
+
+ int day = strtol(date.Mid(5, 2).c_str(), NULL, 10);
+
+ CStdString strMonth = date.Mid(8, 3);
+ int month = 0;
+ for (unsigned int index = 0; index < 12; index++)
+ {
+ if (strMonth.Equals(MONTH_NAMES[index]))
+ {
+ month = index + 1;
+ break;
+ }
+ }
+
+ if (month < 1)
+ return;
+
+ int year = strtol(date.Mid(12, 4).c_str(), NULL, 10);
+ int hour = strtol(date.Mid(17, 2).c_str(), NULL, 10);
+ int min = strtol(date.Mid(20, 2).c_str(), NULL, 10);
+ int sec = strtol(date.Mid(23, 2).c_str(), NULL, 10);
+
+ SetDateTime(year, month, day, hour, min, sec);
+}
+
CStdString CDateTime::GetAsLocalizedTime(const CStdString &format, bool withSeconds) const
{
CStdString strOut;
@@ -1313,9 +1348,6 @@ CDateTime CDateTime::GetAsUTCDateTime() const
return time;
}
-static const char *DAY_NAMES[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-static const char *MONTH_NAMES[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-
CStdString CDateTime::GetAsRFC1123DateTime() const
{
CDateTime time(GetAsUTCDateTime());
View
@@ -172,6 +172,7 @@ class CDateTime : public IArchivable
void SetFromW3CDate(const CStdString &date);
void SetFromUTCDateTime(const CDateTime &dateTime);
void SetFromUTCDateTime(const time_t &dateTime);
+ void SetFromRFC1123DateTime(const CStdString &dateTime);
/*! \brief set from a database datetime format YYYY-MM-DD HH:MM:SS
\sa GetAsDBDateTime()
View
@@ -275,6 +275,7 @@ int CWebServer::HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &r
}
struct MHD_Response *response = NULL;
+ int responseCode = handler->GetHTTPResonseCode();
switch (handler->GetHTTPResponseType())
{
case HTTPNone:
@@ -286,7 +287,7 @@ int CWebServer::HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &r
break;
case HTTPFileDownload:
- ret = CreateFileDownloadResponse(request.connection, handler->GetHTTPResponseFile(), request.method, response);
+ ret = CreateFileDownloadResponse(request.connection, handler->GetHTTPResponseFile(), request.method, response, responseCode);
break;
case HTTPMemoryDownloadNoFreeNoCopy:
@@ -324,7 +325,7 @@ int CWebServer::HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &r
for (multimap<string, string>::const_iterator it = header.begin(); it != header.end(); it++)
MHD_add_response_header(response, it->first.c_str(), it->second.c_str());
- MHD_queue_response(request.connection, handler->GetHTTPResonseCode(), response);
+ MHD_queue_response(request.connection, responseCode, response);
MHD_destroy_response(response);
delete handler;
@@ -354,15 +355,43 @@ int CWebServer::CreateRedirect(struct MHD_Connection *connection, const string &
return MHD_NO;
}
-int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response)
+int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, const string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode)
{
CFile *file = new CFile();
if (file->Open(strURL, READ_NO_CACHE))
{
+ bool getData = true;
if (methodType != HEAD)
{
- response = MHD_create_response_from_callback ( file->GetLength(),
+ if (methodType == GET)
+ {
+ string ifModifiedSince = GetRequestHeaderValue(connection, MHD_HEADER_KIND, "If-Modified-Since");
+ if (!ifModifiedSince.empty())
+ {
+ CDateTime ifModifiedSinceDate;
+ ifModifiedSinceDate.SetFromRFC1123DateTime(ifModifiedSince);
+
+ struct __stat64 statBuffer;
+ if (file->Stat(&statBuffer) == 0)
+ {
+ struct tm *time = localtime((time_t *)&statBuffer.st_mtime);
+ if (time != NULL)
+ {
+ CDateTime lastModified = *time;
+ if (lastModified.GetAsUTCDateTime() <= ifModifiedSinceDate)
+ {
+ getData = false;
+ response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
+ responseCode = MHD_HTTP_NOT_MODIFIED;
+ }
+ }
+ }
+ }
+ }
+
+ if (getData)
+ response = MHD_create_response_from_callback(file->GetLength(),
2048,
&CWebServer::ContentReaderCallback, file,
&CWebServer::ContentReaderFreeCallback);
@@ -371,26 +400,54 @@ int CWebServer::CreateFileDownloadResponse(struct MHD_Connection *connection, co
}
else
{
+ getData = false;
+
CStdString contentLength;
contentLength.Format("%I64d", file->GetLength());
- file->Close();
- delete file;
response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
if (response == NULL)
+ {
+ file->Close();
+ delete file;
return MHD_NO;
+ }
MHD_add_response_header(response, "Content-Length", contentLength);
}
+ // set the Content-Type header
CStdString ext = URIUtils::GetExtension(strURL);
ext = ext.ToLower();
const char *mime = CreateMimeTypeFromExtension(ext.c_str());
if (mime)
MHD_add_response_header(response, "Content-Type", mime);
+ // set the Last-Modified header
+ struct __stat64 statBuffer;
+ if (file->Stat(&statBuffer) == 0)
+ {
+ struct tm *time = localtime((time_t *)&statBuffer.st_mtime);
+ if (time != NULL)
+ {
+ CDateTime lastModified = *time;
+ MHD_add_response_header(response, "Last-Modified", lastModified.GetAsRFC1123DateTime());
+ }
+ }
+
+ // set the Expires header
CDateTime expiryTime = CDateTime::GetCurrentDateTime();
- expiryTime += CDateTimeSpan(1, 0, 0, 0);
+ if (mime && strncmp(mime, "text/html", 9) == 0)
+ expiryTime += CDateTimeSpan(1, 0, 0, 0);
+ else
+ expiryTime += CDateTimeSpan(365, 0, 0, 0);
MHD_add_response_header(response, "Expires", expiryTime.GetAsRFC1123DateTime());
+
+ // only close the CFile instance if libmicrohttpd doesn't have to grab the data of the file
+ if (!getData)
+ {
+ file->Close();
+ delete file;
+ }
}
else
{
View
@@ -92,7 +92,7 @@ class CWebServer : public JSONRPC::ITransportLayer
static int HandleRequest(IHTTPRequestHandler *handler, const HTTPRequest &request);
static void ContentReaderFreeCallback (void *cls);
static int CreateRedirect(struct MHD_Connection *connection, const std::string &strURL, struct MHD_Response *&response);
- static int CreateFileDownloadResponse(struct MHD_Connection *connection, const std::string &strURL, HTTPMethod methodType, struct MHD_Response *&response);
+ static int CreateFileDownloadResponse(struct MHD_Connection *connection, const std::string &strURL, HTTPMethod methodType, struct MHD_Response *&response, int &responseCode);
static int CreateErrorResponse(struct MHD_Connection *connection, int responseType, HTTPMethod method, struct MHD_Response *&response);
static int CreateMemoryDownloadResponse(struct MHD_Connection *connection, void *data, size_t size, bool free, bool copy, struct MHD_Response *&response);