Files
boost_beast/example/server-framework/file_service.hpp

249 lines
7.7 KiB
C++
Raw Normal View History

//
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
#define BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
#include "file_body.hpp"
#include "framework.hpp"
#include <beast/core/string.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <beast/http/string_body.hpp>
#include <boost/filesystem/path.hpp>
#include <string>
namespace framework {
/** An HTTP service which delivers files from a root directory.
This service will accept GET and HEAD requests for files,
and deliver them as responses. The service constructs with
the location on the file system to act as the root for the
tree of files to serve.
Meets the requirements of @b Service
*/
class file_service
{
// The path to serve files from
boost::filesystem::path root_;
// The name to use in the Server HTTP field
std::string server_;
public:
/** Constructor
@param root A path with files to serve. A GET request
for "/" will try to deliver the file "/index.html".
@param The string to use in the Server HTTP field.
*/
explicit
file_service(
boost::filesystem::path const& root,
beast::string_view server)
: root_(root)
, server_(server)
{
}
/** Initialize the service.
This provides an opportunity for the service to perform
initialization which may fail, while reporting an error
code instead of throwing an exception from the constructor.
@note This is needed for to meet the requirements for @b Service
*/
void
init(error_code& ec)
{
// This is required by the error_code specification
//
ec = {};
}
/** Process a request.
@note This is needed for to meet the requirements for @b Service
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&&,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const& send) const
{
// Check the method and take action
switch(req.method())
{
case beast::http::verb::get:
{
// For GET requests we deliver the actual file
boost::filesystem::path rel_path(req.target().to_string());
// Give them the root web page if the target is "/"
if(rel_path == "/")
rel_path = "/index.html";
// Calculate full path from root
boost::filesystem::path full_path = root_ / rel_path;
// Make sure the file is there
if(boost::filesystem::exists(full_path))
{
// Send the file
send(get(req, full_path));
}
else
{
// Send a Not Found result
send(not_found(req, rel_path));
}
// Indicate that we handled the request
return true;
}
case beast::http::verb::head:
{
// We are just going to give them the headers they
// would otherwise get, but without the body.
boost::filesystem::path rel_path(req.target().to_string());
if(rel_path == "/")
rel_path = "/index.html";
// Calculate full path from root
boost::filesystem::path full_path = root_ / rel_path;
// Make sure the file is there
if(! boost::filesystem::exists(full_path))
{
// Send a HEAD response
send(head(req, full_path));
}
else
{
// Send a Not Found result
send(not_found(req, rel_path));
}
// Indicate that we handled the request
return true;
}
default:
break;
}
// We didn't handle this request, so return false to
// inform the service list to try the next service.
//
return false;
}
private:
// Return a reasonable mime type based on the extension of a file.
//
beast::string_view
mime_type(boost::filesystem::path const& path) const
{
using beast::iequals;
auto const ext = path.extension().string();
if(iequals(ext, ".txt")) return "text/plain";
if(iequals(ext, ".htm")) return "text/html";
if(iequals(ext, ".html")) return "text/html";
if(iequals(ext, ".php")) return "text/html";
if(iequals(ext, ".css")) return "text/css";
if(iequals(ext, ".js")) return "application/javascript";
if(iequals(ext, ".json")) return "application/json";
if(iequals(ext, ".xml")) return "application/xml";
if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
if(iequals(ext, ".flv")) return "video/x-flv";
if(iequals(ext, ".png")) return "image/png";
if(iequals(ext, ".jpe")) return "image/jpeg";
if(iequals(ext, ".jpeg")) return "image/jpeg";
if(iequals(ext, ".jpg")) return "image/jpeg";
if(iequals(ext, ".gif")) return "image/gif";
if(iequals(ext, ".bmp")) return "image/bmp";
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
if(iequals(ext, ".tiff")) return "image/tiff";
if(iequals(ext, ".tif")) return "image/tiff";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}
// Return an HTTP Not Found response
//
template<class Body, class Fields>
beast::http::response<beast::http::string_body>
not_found(beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& rel_path) const
{
beast::http::response<beast::http::string_body> res;
res.version = req.version;
res.result(beast::http::status::not_found);
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, "text/html");
res.body = "The file was not found"; // VFALCO append rel_path
res.prepare();
return res;
}
// Return a file response to an HTTP GET request
//
template<class Body, class Fields>
beast::http::response<file_body>
get(beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path) const
{
beast::http::response<file_body> res;
res.version = req.version;
res.result(beast::http::status::ok);
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, mime_type(full_path));
res.body = full_path;
res.prepare();
return res;
}
// Return a response to an HTTP HEAD request
//
template<class Body, class Fields>
beast::http::response<beast::http::empty_body>
head(beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path) const
{
beast::http::response<beast::http::empty_body> res;
res.version = req.version;
res.result(beast::http::status::ok);
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, mime_type(full_path));
// Set the Content-Length field manually. We don't have a body,
// but this is a response to a HEAD request so we include the
// content length anyway.
//
res.set(beast::http::field::content_length, file_body::size(full_path));
return res;
}
};
} // framework
#endif