You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by be...@apache.org on 2012/10/13 01:20:52 UTC
svn commit: r1397741 - in /incubator/mesos/trunk: src/files/ src/tests/
third_party/libprocess/include/process/
third_party/libprocess/include/stout/ third_party/libprocess/src/
Author: benh
Date: Fri Oct 12 23:20:51 2012
New Revision: 1397741
URL: http://svn.apache.org/viewvc?rev=1397741&view=rev
Log:
Added a /download.json endpoint to "files" abstraction.
From: Ben Mahler <be...@gmail.com>
Review: https://reviews.apache.org/r/7123
Modified:
incubator/mesos/trunk/src/files/files.cpp
incubator/mesos/trunk/src/tests/files_tests.cpp
incubator/mesos/trunk/src/tests/utils.hpp
incubator/mesos/trunk/third_party/libprocess/include/process/http.hpp
incubator/mesos/trunk/third_party/libprocess/include/stout/os.hpp
incubator/mesos/trunk/third_party/libprocess/src/decoder.hpp
incubator/mesos/trunk/third_party/libprocess/src/process.cpp
Modified: incubator/mesos/trunk/src/files/files.cpp
URL: http://svn.apache.org/viewvc/incubator/mesos/trunk/src/files/files.cpp?rev=1397741&r1=1397740&r2=1397741&view=diff
==============================================================================
--- incubator/mesos/trunk/src/files/files.cpp (original)
+++ incubator/mesos/trunk/src/files/files.cpp Fri Oct 12 23:20:51 2012
@@ -7,6 +7,7 @@
#include <process/dispatch.hpp>
#include <process/future.hpp>
#include <process/http.hpp>
+#include <process/mime.hpp>
#include <process/process.hpp>
#include <stout/hashmap.hpp>
@@ -58,6 +59,7 @@ private:
// HTTP endpoints.
Future<Response> browse(const Request& request);
Future<Response> read(const Request& request);
+ Future<Response> download(const Request& request);
Future<Response> debug(const Request& request);
// Resolves the virtual path to an actual path.
@@ -80,6 +82,7 @@ void FilesProcess::initialize()
{
route("/browse.json", &FilesProcess::browse);
route("/read.json", &FilesProcess::read);
+ route("/download.json", &FilesProcess::download);
route("/debug.json", &FilesProcess::debug);
}
@@ -131,7 +134,7 @@ Future<Response> FilesProcess::browse(co
Result<string> resolvedPath = resolve(path.get());
if (resolvedPath.isError()) {
- return InternalServerError(resolvedPath.error());
+ return InternalServerError(resolvedPath.error() + ".\n");
} else if (resolvedPath.isNone()) {
return NotFound();
}
@@ -173,7 +176,7 @@ Future<Response> FilesProcess::read(cons
if (request.query.get("offset").isSome()) {
Try<off_t> result = numify<off_t>(request.query.get("offset").get());
if (result.isError()) {
- return BadRequest("Failed to parse offset: " + result.error());
+ return BadRequest("Failed to parse offset: " + result.error() + ".\n");
}
offset = result.get();
}
@@ -183,7 +186,7 @@ Future<Response> FilesProcess::read(cons
if (request.query.get("length").isSome()) {
Try<ssize_t> result = numify<ssize_t>(request.query.get("length").get());
if (result.isError()) {
- return BadRequest("Failed to parse length: " + result.error());
+ return BadRequest("Failed to parse length: " + result.error() + ".\n");
}
length = result.get();
}
@@ -191,14 +194,14 @@ Future<Response> FilesProcess::read(cons
Result<string> resolvedPath = resolve(path.get());
if (resolvedPath.isError()) {
- return BadRequest(resolvedPath.error());
+ return BadRequest(resolvedPath.error() + ".\n");
} else if (!resolvedPath.isSome()) {
return NotFound();
}
// Don't read directories.
if (os::isdir(resolvedPath.get())) {
- return BadRequest("Cannot read a directory.");
+ return BadRequest("Cannot read a directory.\n");
}
// TODO(benh): Cache file descriptors so we aren't constantly
@@ -209,7 +212,7 @@ Future<Response> FilesProcess::read(cons
string error = strings::format("Failed to open file at '%s': %s",
resolvedPath.get(), fd.error()).get();
LOG(WARNING) << error;
- return InternalServerError(error);
+ return InternalServerError(error + ".\n");
}
off_t size = lseek(fd.get(), 0, SEEK_END);
@@ -219,7 +222,7 @@ Future<Response> FilesProcess::read(cons
resolvedPath.get(), strerror(errno)).get();
LOG(WARNING) << error;
close(fd.get());
- return InternalServerError(error);
+ return InternalServerError(error + ".\n");
}
if (offset == -1) {
@@ -239,7 +242,7 @@ Future<Response> FilesProcess::read(cons
resolvedPath.get(), strerror(errno)).get();
LOG(WARNING) << error;
close(fd.get());
- return InternalServerError(error);
+ return InternalServerError(error + ".\n");
}
// Read length bytes (or to EOF).
@@ -258,7 +261,7 @@ Future<Response> FilesProcess::read(cons
LOG(WARNING) << error;
delete[] temp;
close(fd.get());
- return InternalServerError(error);
+ return InternalServerError(error + ".\n");
} else {
object.values["offset"] = offset;
object.values["length"] = length;
@@ -276,6 +279,53 @@ Future<Response> FilesProcess::read(cons
}
+Future<Response> FilesProcess::download(const Request& request)
+{
+ Option<string> path = request.query.get("path");
+
+ if (!path.isSome() || path.get().empty()) {
+ return BadRequest("Expecting 'path=value' in query.\n");
+ }
+
+ Result<string> resolvedPath = resolve(path.get());
+
+ if (resolvedPath.isError()) {
+ return BadRequest(resolvedPath.error() + ".\n");
+ } else if (!resolvedPath.isSome()) {
+ return NotFound();
+ }
+
+ // Don't download directories.
+ if (os::isdir(resolvedPath.get())) {
+ return BadRequest("Cannot download a directory.\n");
+ }
+
+ Try<string> basename = os::basename(resolvedPath.get());
+ if (basename.isError()) {
+ LOG(ERROR) << basename.error();
+ return InternalServerError(basename.error() + ".\n");
+ }
+
+ OK response;
+ response.type = response.PATH;
+ response.path = resolvedPath.get();
+ response.headers["Content-Type"] = "application/octet-stream";
+ response.headers["Content-Disposition"] =
+ strings::format("attachment; filename=%s", basename.get()).get();
+
+ // Attempt to detect the mime type.
+ size_t index = basename.get().find_last_of('.');
+ if (index != string::npos) {
+ string extension = basename.get().substr(index);
+ if (mime::types.count(extension) > 0) {
+ response.headers["Content-Type"] = mime::types[extension];
+ }
+ }
+
+ return response;
+}
+
+
Future<Response> FilesProcess::debug(const Request& request)
{
JSON::Object object;
@@ -327,9 +377,9 @@ Result<string> FilesProcess::resolve(con
// accessible.
Try<string> result = os::realpath(path);
if (result.isError()) {
- return Result<string>::error("Cannot resolve path.");
+ return Result<string>::error("Cannot resolve path");
} else if (!strings::startsWith(result.get(), paths[prefix])) {
- return Result<string>::error("Resolved path is inaccessible.");
+ return Result<string>::error("Resolved path is inaccessible");
}
path = result.get();
Modified: incubator/mesos/trunk/src/tests/files_tests.cpp
URL: http://svn.apache.org/viewvc/incubator/mesos/trunk/src/tests/files_tests.cpp?rev=1397741&r1=1397740&r2=1397741&view=diff
==============================================================================
--- incubator/mesos/trunk/src/tests/files_tests.cpp (original)
+++ incubator/mesos/trunk/src/tests/files_tests.cpp Fri Oct 12 23:20:51 2012
@@ -16,6 +16,8 @@
* limitations under the License.
*/
+#include <string>
+
#include <gmock/gmock.h>
#include <process/future.hpp>
@@ -40,6 +42,8 @@ using process::http::NotFound;
using process::http::OK;
using process::http::Response;
+using std::string;
+
TEST_WITH_WORKDIR(FilesTest, AttachTest)
{
@@ -86,14 +90,14 @@ TEST_WITH_WORKDIR(FilesTest, ReadTest)
EXPECT_RESPONSE_STATUS_WILL_EQ(BadRequest().status, response);
EXPECT_RESPONSE_BODY_WILL_EQ(
- "Failed to parse offset: Failed to convert 'hello' to number",
+ "Failed to parse offset: Failed to convert 'hello' to number.\n",
response);
response = process::http::get(pid, "read.json?path=none&length=hello");
EXPECT_RESPONSE_STATUS_WILL_EQ(BadRequest().status, response);
EXPECT_RESPONSE_BODY_WILL_EQ(
- "Failed to parse length: Failed to convert 'hello' to number",
+ "Failed to parse length: Failed to convert 'hello' to number.\n",
response);
// Now write a file.
@@ -247,3 +251,38 @@ TEST_WITH_WORKDIR(FilesTest, BrowseTest)
NotFound().status,
process::http::get(pid, "browse.json?path=missing"));
}
+
+
+TEST_WITH_WORKDIR(FilesTest, DownloadTest)
+{
+ Files files;
+ const process::PID<>& pid = files.pid();
+
+ // This is a one-pixel black gif image.
+ char gifData[] = {
+ 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x01, 0x00, 0x01, 0x00, 0x91, 0x00,
+ 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x02,
+ 0x02, 0x4c, 0x01, 0x00, 0x3b, 0x00
+ };
+ string data(gifData, sizeof(gifData));
+
+ ASSERT_TRUE(os::write("binary", "no file extension").isSome());
+ ASSERT_TRUE(os::write("black.gif", data).isSome());
+ EXPECT_FUTURE_WILL_SUCCEED(files.attach("binary", "binary"));
+ EXPECT_FUTURE_WILL_SUCCEED(files.attach("black.gif", "black.gif"));
+
+ Future<Response> response =
+ process::http::get(pid, "download.json?path=binary");
+ EXPECT_RESPONSE_STATUS_WILL_EQ(OK().status, response);
+ EXPECT_RESPONSE_HEADER_WILL_EQ(
+ "application/octet-stream",
+ "Content-Type",
+ response);
+ EXPECT_RESPONSE_BODY_WILL_EQ("no file extension", response);
+
+ response = process::http::get(pid, "download.json?path=black.gif");
+ EXPECT_RESPONSE_STATUS_WILL_EQ(OK().status, response);
+ EXPECT_RESPONSE_HEADER_WILL_EQ("image/gif", "Content-Type", response);
+ EXPECT_RESPONSE_BODY_WILL_EQ(data, response);
+}
Modified: incubator/mesos/trunk/src/tests/utils.hpp
URL: http://svn.apache.org/viewvc/incubator/mesos/trunk/src/tests/utils.hpp?rev=1397741&r1=1397740&r2=1397741&view=diff
==============================================================================
--- incubator/mesos/trunk/src/tests/utils.hpp (original)
+++ incubator/mesos/trunk/src/tests/utils.hpp Fri Oct 12 23:20:51 2012
@@ -188,6 +188,15 @@ inline void EXPECT_RESPONSE_BODY_WILL_EQ
}
+#define EXPECT_RESPONSE_HEADER_WILL_EQ(expected, headerKey, future) \
+ do { \
+ typeof(future) _future = future; \
+ ASSERT_FUTURE_WILL_SUCCEED(future); \
+ EXPECT_TRUE(_future.get().headers.contains(headerKey)); \
+ EXPECT_EQ(expected, _future.get().headers.find(headerKey)->second); \
+ } while (false);
+
+
/**
* Definition of a mock Scheduler to be used in tests with gmock.
*/
Modified: incubator/mesos/trunk/third_party/libprocess/include/process/http.hpp
URL: http://svn.apache.org/viewvc/incubator/mesos/trunk/third_party/libprocess/include/process/http.hpp?rev=1397741&r1=1397740&r2=1397741&view=diff
==============================================================================
--- incubator/mesos/trunk/third_party/libprocess/include/process/http.hpp (original)
+++ incubator/mesos/trunk/third_party/libprocess/include/process/http.hpp Fri Oct 12 23:20:51 2012
@@ -103,6 +103,16 @@ struct OK : Response
};
+struct TemporaryRedirect : Response
+{
+ TemporaryRedirect(const std::string& url) : Response("")
+ {
+ status = "307 Temporary Redirect";
+ headers["Location"] = url;
+ }
+};
+
+
struct BadRequest : Response
{
BadRequest(const std::string& body = "") : Response(body)
@@ -139,16 +149,6 @@ struct ServiceUnavailable : Response
};
-struct TemporaryRedirect : Response
-{
- TemporaryRedirect(const std::string& url) : Response("")
- {
- status = "307 Temporary Redirect";
- headers["Location"] = url;
- }
-};
-
-
namespace query {
// Parses an HTTP query string into a map. For example:
Modified: incubator/mesos/trunk/third_party/libprocess/include/stout/os.hpp
URL: http://svn.apache.org/viewvc/incubator/mesos/trunk/third_party/libprocess/include/stout/os.hpp?rev=1397741&r1=1397740&r2=1397741&view=diff
==============================================================================
--- incubator/mesos/trunk/third_party/libprocess/include/stout/os.hpp (original)
+++ incubator/mesos/trunk/third_party/libprocess/include/stout/os.hpp Fri Oct 12 23:20:51 2012
@@ -247,6 +247,8 @@ inline Try<Nothing> write(const std::str
// Read the contents of the file from its current offset
// and return it as a string.
+// TODO(bmahler): Change this to a Try<std::string> since none()
+// is equivalent to an empty string.
inline Result<std::string> read(int fd)
{
// Get the size of the file.
Modified: incubator/mesos/trunk/third_party/libprocess/src/decoder.hpp
URL: http://svn.apache.org/viewvc/incubator/mesos/trunk/third_party/libprocess/src/decoder.hpp?rev=1397741&r1=1397740&r2=1397741&view=diff
==============================================================================
--- incubator/mesos/trunk/third_party/libprocess/src/decoder.hpp (original)
+++ incubator/mesos/trunk/third_party/libprocess/src/decoder.hpp Fri Oct 12 23:20:51 2012
@@ -95,6 +95,12 @@ private:
static int on_headers_complete(http_parser* p)
{
DataDecoder* decoder = (DataDecoder*) p->data;
+
+ // Add final header.
+ decoder->request->headers[decoder->field] = decoder->value;
+ decoder->field.clear();
+ decoder->value.clear();
+
decoder->request->method = http_method_str((http_method) decoder->parser.method);
decoder->request->keepAlive = http_should_keep_alive(&decoder->parser);
return 0;
@@ -275,6 +281,13 @@ private:
static int on_headers_complete(http_parser* p)
{
+ ResponseDecoder* decoder = (ResponseDecoder*) p->data;
+
+ // Add final header.
+ decoder->response->headers[decoder->field] = decoder->value;
+ decoder->field.clear();
+ decoder->value.clear();
+
return 0;
}
Modified: incubator/mesos/trunk/third_party/libprocess/src/process.cpp
URL: http://svn.apache.org/viewvc/incubator/mesos/trunk/third_party/libprocess/src/process.cpp?rev=1397741&r1=1397740&r2=1397741&view=diff
==============================================================================
--- incubator/mesos/trunk/third_party/libprocess/src/process.cpp (original)
+++ incubator/mesos/trunk/third_party/libprocess/src/process.cpp Fri Oct 12 23:20:51 2012
@@ -3195,7 +3195,8 @@ Future<Response> decode(const string& bu
for (size_t i = 0; i < responses.size(); ++i) {
delete responses[i];
}
- return Future<Response>::failed("Failed to decode HTTP response");
+ return Future<Response>::failed(
+ "Failed to decode HTTP response:\n" + buffer + "\n");
} else if (responses.size() > 1) {
PLOG(ERROR) << "Received more than 1 HTTP Response";
}