You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ti...@apache.org on 2015/07/09 17:40:06 UTC
[2/2] mesos git commit: Added support for cache control in libprocess
when dealing with static files.
Added support for cache control in libprocess when dealing with static files.
When serving a static file, libprocess returns the header
`Last-Modified` which is used by browsers to control Cache. When a http
request arrives containing the header `If-Modified-Since`, a response
`304 Not Modified` is returned if the date in the request and the
modification time (as returned by doing `stat` in the file) coincide.
Unit tests added.
Review: https://reviews.apache.org/r/30032
Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/d0300e1a
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/d0300e1a
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/d0300e1a
Branch: refs/heads/master
Commit: d0300e1a47d1ba5d6714957fc258ab125fd53ed1
Parents: 2dc9f5e
Author: Alexander Rojas <al...@mesosphere.io>
Authored: Thu Jul 9 17:30:02 2015 +0200
Committer: Till Toenshoff <to...@me.com>
Committed: Thu Jul 9 17:35:18 2015 +0200
----------------------------------------------------------------------
3rdparty/libprocess/include/process/http.hpp | 16 ++++
3rdparty/libprocess/src/process.cpp | 62 ++++++++++++-
3rdparty/libprocess/src/tests/process_tests.cpp | 96 ++++++++++++++++++++
3 files changed, 173 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mesos/blob/d0300e1a/3rdparty/libprocess/include/process/http.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/http.hpp b/3rdparty/libprocess/include/process/http.hpp
index 8d9adc5..32a2d84 100644
--- a/3rdparty/libprocess/include/process/http.hpp
+++ b/3rdparty/libprocess/include/process/http.hpp
@@ -18,6 +18,7 @@
#include <process/future.hpp>
#include <process/owned.hpp>
#include <process/pid.hpp>
+#include <process/time.hpp>
#include <stout/error.hpp>
#include <stout/hashmap.hpp>
@@ -367,6 +368,21 @@ struct Accepted : Response
};
+struct NotModified : Response
+{
+ explicit NotModified(const Time& lastModified)
+ {
+ status = "304 Not Modified";
+
+ std::string mtime = stringify(RFC1123(lastModified));
+
+ if (!mtime.empty()) {
+ headers["Last-Modified"] = mtime;
+ }
+ }
+};
+
+
struct TemporaryRedirect : Response
{
explicit TemporaryRedirect(const std::string& url)
http://git-wip-us.apache.org/repos/asf/mesos/blob/d0300e1a/3rdparty/libprocess/src/process.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/process.cpp b/3rdparty/libprocess/src/process.cpp
index c3e1763..9037b98 100644
--- a/3rdparty/libprocess/src/process.cpp
+++ b/3rdparty/libprocess/src/process.cpp
@@ -8,6 +8,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
@@ -80,6 +81,8 @@
#include <stout/thread.hpp>
#include <stout/unreachable.hpp>
+#include <stout/os/stat.hpp>
+
#include "config.hpp"
#include "decoder.hpp"
#include "encoder.hpp"
@@ -93,6 +96,7 @@
using namespace process::firewall;
using namespace process::metrics::internal;
+using process::RFC1123;
using process::wait; // Necessary on some OS's to disambiguate.
using process::http::Accepted;
@@ -100,6 +104,7 @@ using process::http::BadRequest;
using process::http::Forbidden;
using process::http::InternalServerError;
using process::http::NotFound;
+using process::http::NotModified;
using process::http::OK;
using process::http::Request;
using process::http::Response;
@@ -3009,6 +3014,53 @@ void ProcessBase::visit(const HttpEvent& event)
}
}
+ // Get the modification time of the file.
+ // TODO(arojas): Use Path.mtime() once it is available.
+ const Try<long> systemMtime = os::stat::mtime(response.path);
+ const Try<Time> mtime = systemMtime.isSome()
+ ? Time::create(systemMtime.get())
+ : Error(systemMtime.error());
+
+ bool modified = true;
+
+ // In order to use the HTTP cache the following is needed:
+ // 1. Request must include the header 'If-Modified-Since' and it
+ // must be a valid date.
+ // 2. Modification time from the resource must be determined.
+ // 3. Both times must be equal.
+ // However, if any of the conditions are not met, there's no need
+ // to propagate the error. If a condition is not satisfied, the
+ // whole file is returned and the appropriate reason is logged.
+ if (mtime.isSome() &&
+ event.request->headers.contains("If-Modified-Since")) {
+ tm clientMtime = {};
+
+ if (strptime(
+ event.request->headers["If-Modified-Since"].c_str(),
+ "%a, %d %b %Y %T %Z",
+ &clientMtime) != NULL) {
+ const time_t client = std::mktime(&clientMtime);
+ const time_t server = static_cast<time_t>(mtime.get().secs());
+
+ if (client != -1 && client == server) {
+ modified = false;
+ } else if (client == -1 || server == -1) {
+ VLOG(1) << "Failed to create 'time_t' through mktime";
+ }
+ } else {
+ VLOG(1) << "Failed to parse time.";
+ }
+ }
+
+ // Provide the Last-Modified header to the client so it can set up
+ // the cache.
+ if (mtime.isSome()) {
+ response.headers["Last-Modified"] = stringify(RFC1123(mtime.get()));
+ } else {
+ VLOG(1) << "Failed to get mtime for " << response.path << ": "
+ << mtime.error();
+ }
+
// TODO(benh): Use "text/plain" for assets that don't have an
// extension or we don't have a mapping for? It might be better to
// just let the browser guess (or do it's own default).
@@ -3018,7 +3070,15 @@ void ProcessBase::visit(const HttpEvent& event)
// Enqueue the response with the HttpProxy so that it respects the
// order of requests to account for HTTP/1.1 pipelining.
- dispatch(proxy, &HttpProxy::enqueue, response, *event.request);
+ if (modified) {
+ dispatch(proxy, &HttpProxy::enqueue, response, *event.request);
+ } else {
+ dispatch(
+ proxy,
+ &HttpProxy::enqueue,
+ NotModified(mtime.get()),
+ *event.request);
+ }
} else {
VLOG(1) << "Returning '404 Not Found' for '" << event.request->path << "'";
http://git-wip-us.apache.org/repos/asf/mesos/blob/d0300e1a/3rdparty/libprocess/src/tests/process_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/tests/process_tests.cpp b/3rdparty/libprocess/src/tests/process_tests.cpp
index f123ed4..4fa834d 100644
--- a/3rdparty/libprocess/src/tests/process_tests.cpp
+++ b/3rdparty/libprocess/src/tests/process_tests.cpp
@@ -1,3 +1,5 @@
+#include <time.h>
+
#include <arpa/inet.h>
#include <gmock/gmock.h>
@@ -1679,6 +1681,100 @@ TEST(ProcessTest, Provide)
}
+// Requests a static resource via HTTP and validates if proper answers
+// are returned for both cache hits and misses.
+TEST(ProcessTest, Cache)
+{
+ const Try<string>& mkdtemp = os::mkdtemp();
+ ASSERT_SOME(mkdtemp);
+
+ const string LOREM_IPSUM =
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do "
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad "
+ "minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip "
+ "ex ea commodo consequat. Duis aute irure dolor in reprehenderit in "
+ "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur "
+ "sint occaecat cupidatat non proident, sunt in culpa qui officia "
+ "deserunt mollit anim id est laborum.";
+
+ const string path = path::join(mkdtemp.get(), "lorem.txt");
+ ASSERT_SOME(os::write(path, LOREM_IPSUM));
+
+ FileServer server(path);
+ PID<FileServer> pid = spawn(server);
+
+ // First request.
+ Future<http::Response> response = http::get(pid);
+
+ AWAIT_READY(response);
+
+ // First request had no information about the served file, so a 200
+ // code is expected as well as the full body.
+ EXPECT_EQ("200 OK", response.get().status);
+ ASSERT_TRUE(response.get().headers.contains("Last-Modified"));
+
+ // Request includes proper cache headers.
+ const std::string mtime = response.get().headers.get("Last-Modified").get();
+
+ hashmap<std::string, std::string> headers;
+ headers.put("Cache-Control", "max-age=0");
+ headers.put("If-Modified-Since", mtime);
+
+ response = http::get(pid, None(), None(), headers);
+
+ AWAIT_READY(response);
+ ASSERT_EQ("304 Not Modified", response.get().status);
+ ASSERT_TRUE(response.get().headers.contains("Last-Modified"));
+ EXPECT_SOME_EQ(mtime, response.get().headers.get("Last-Modified"));
+
+ static const char HTTP_DATE[] = "%a, %d %b %Y %T %Z";
+
+ // Use a different modification time for the request. The whole file
+ // should be served.
+ tm tmTime = {};
+ strptime(mtime.c_str(), HTTP_DATE, &tmTime);
+ time_t earlierTime = mktime(&tmTime);
+ ASSERT_NE(-1, earlierTime);
+
+ // Just an arbitrary earlier time than the mtime. In this case, 100
+ // seconds.
+ earlierTime -= 100;
+
+ // Transform the time back to a string.
+ ASSERT_TRUE(NULL != gmtime_r(&earlierTime, &tmTime));
+ static const size_t BUFFER_SIZE = 200;
+ char buffer[BUFFER_SIZE] = {0};
+ ASSERT_NE(0, strftime(buffer, BUFFER_SIZE, HTTP_DATE, &tmTime));
+ std::string strTime = buffer;
+ headers.put("If-Modified-Since", strTime);
+
+ response = http::get(pid, None(), None(), headers);
+
+ AWAIT_READY(response);
+
+ // Request 'If-Modified-Since' header doesn't match the server's
+ // mtime. Full file expected.
+ EXPECT_EQ("200 OK", response.get().status);
+ EXPECT_TRUE(response.get().headers.contains("Last-Modified"));
+ EXPECT_EQ(LOREM_IPSUM, response.get().body);
+
+ // Finally, test with an unparseable date.
+ headers.put("If-Modified-Since", "XXX-YYY-ZZZ PPP");
+ response = http::get(pid, None(), None(), headers);
+
+ // Request 'If-Modified-Since' header doesn't match the server's
+ // mtime. Full file expected.
+ EXPECT_EQ("200 OK", response.get().status);
+ EXPECT_TRUE(response.get().headers.contains("Last-Modified"));
+ EXPECT_EQ(LOREM_IPSUM, response.get().body);
+
+ terminate(server);
+ wait(server);
+
+ ASSERT_SOME(os::rmdir(path));
+}
+
+
int baz(string s) { return 42; }
Future<int> bam(string s) { return 42; }