-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Http: Add HttpWebServer to serve files from a directory
- Loading branch information
Showing
6 changed files
with
221 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright (c) Stefano Cristiano | ||
// SPDX-License-Identifier: MIT | ||
#include "HttpWebServer.h" | ||
#include "../FileSystem/FileSystem.h" | ||
#include "../FileSystem/Path.h" | ||
#include "../Strings/StringBuilder.h" | ||
|
||
struct SC::HttpWebServer::Internal | ||
{ | ||
static Result writeGMTHeaderTime(StringView headerName, HttpResponse& response, | ||
const Time::Absolute::ParseResult& local); | ||
static StringView getContentType(const StringView extension); | ||
|
||
static Result readFile(StringView initialDirectory, HttpRequest& request, HttpResponse& response); | ||
}; | ||
|
||
SC::Result SC::HttpWebServer::init(StringView directoryToServe) | ||
{ | ||
SC_TRY_MSG(FileSystem().existsAndIsDirectory(directoryToServe), "Invalid directory"); | ||
SC_TRY(directory.assign(directoryToServe)); | ||
return Result(true); | ||
} | ||
|
||
SC::Result SC::HttpWebServer::stopAsync() { return Result(true); } | ||
|
||
void SC::HttpWebServer::serveFile(HttpRequest& request, HttpResponse& response) | ||
{ | ||
if (not Internal::readFile(directory.view(), request, response)) | ||
{ | ||
SC_TRUST_RESULT(response.startResponse(404)); | ||
SC_TRUST_RESULT(response.end("Error")); | ||
} | ||
} | ||
|
||
SC::Result SC::HttpWebServer::Internal::readFile(StringView directory, HttpRequest& request, HttpResponse& response) | ||
{ | ||
if (not request.getURL().startsWith("/")) | ||
{ | ||
return Result::Error("Wrong url"); | ||
} | ||
StringView url = request.getURL().sliceStart(1); | ||
if (url.isEmpty()) | ||
{ | ||
url = "index.html"; | ||
} | ||
FileSystem fileSystem; | ||
SC_TRY(fileSystem.init(directory)); | ||
if (fileSystem.existsAndIsFile(url)) | ||
{ | ||
FileSystem::FileStat fileStat; | ||
SC_TRY(fileSystem.getFileStat(url, fileStat)); | ||
StringView name, extension; | ||
SC_TRY(Path::parseNameExtension(url, name, extension)); | ||
Vector<char> data; | ||
SC_TRY(fileSystem.read(url, data)); | ||
SC_TRY(response.startResponse(200)); | ||
SC_TRY(response.addHeader("Connection", "Closed")); | ||
SC_TRY(response.addHeader("Content-Type", Internal::getContentType(extension))); | ||
SC_TRY(response.addHeader("Server", "SC")); | ||
Time::Absolute::ParseResult local; | ||
SC_TRY(Time::Absolute::now().parseUTC(local)); | ||
SC_TRY(Internal::writeGMTHeaderTime("Date", response, local)); | ||
SC_TRY(fileStat.modifiedTime.parseUTC(local)); | ||
SC_TRY(Internal::writeGMTHeaderTime("Last-Modified", response, local)); | ||
SC_TRY(response.end(data.toSpanConst())); | ||
} | ||
return Result(true); | ||
} | ||
|
||
SC::Result SC::HttpWebServer::Internal::writeGMTHeaderTime(StringView headerName, HttpResponse& response, | ||
const Time::Absolute::ParseResult& local) | ||
{ | ||
SmallString<512> buffer; | ||
SC_TRY(StringBuilder(buffer).format("{}, {:02} {} {} {:02}:{:02}:{:02} GMT", local.getDay(), local.dayOfMonth, | ||
local.getMonth(), local.year, local.hour, local.minutes, local.seconds)); | ||
|
||
SC_TRY(response.addHeader(headerName, buffer.view())); | ||
return Result(true); | ||
} | ||
|
||
SC::StringView SC::HttpWebServer::Internal::getContentType(const StringView extension) | ||
{ | ||
if (extension == "htm" or extension == "html") | ||
{ | ||
return "text/html"; | ||
} | ||
if (extension == "css") | ||
{ | ||
return "text/css"; | ||
} | ||
if (extension == "png") | ||
{ | ||
return "image/png"; | ||
} | ||
if (extension == "jpeg" or extension == "jpg") | ||
{ | ||
return "image/jpg"; | ||
} | ||
if (extension == "svg") | ||
{ | ||
return "image/svg+xml"; | ||
} | ||
return "text/html"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright (c) Stefano Cristiano | ||
// SPDX-License-Identifier: MIT | ||
#pragma once | ||
#include "../Strings/String.h" | ||
#include "HttpServer.h" | ||
|
||
namespace SC | ||
{ | ||
struct SC_COMPILER_EXPORT HttpWebServer; | ||
} | ||
|
||
/// @brief Http web server helps statically serves files from a directory. | ||
/// @n | ||
/// It can be used in conjunction with SC::HttpServer, by calling SC::HttpWebServer::serveFile | ||
/// inside the SC::HttpServer::onRequest callback to statically serve files. | ||
/// | ||
/// @see SC::HttpServer | ||
/// | ||
/// \snippet Libraries/Http/Tests/HttpWebServerTest.cpp HttpWebServerSnippet | ||
struct SC::HttpWebServer | ||
{ | ||
/// @brief Initialize the web server on the given file system directory to serve | ||
Result init(StringView directoryToServe); | ||
|
||
/// @brief Release all resources allocated by this web server | ||
Result stopAsync(); | ||
|
||
/// @brief Serve the efile requested by this Http Client on its channel | ||
/// Call this method in response to HttpServer::onRequest to serve a file | ||
void serveFile(HttpRequest& request, HttpResponse& response); | ||
|
||
private: | ||
String directory; | ||
|
||
struct Internal; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright (c) Stefano Cristiano | ||
// SPDX-License-Identifier: MIT | ||
#include "../HttpWebServer.h" | ||
#include "../../FileSystem/FileSystem.h" | ||
#include "../../Testing/Testing.h" | ||
#include "../HttpClient.h" | ||
namespace SC | ||
{ | ||
struct HttpWebServerTest; | ||
} // namespace SC | ||
|
||
struct SC::HttpWebServerTest : public SC::TestCase | ||
{ | ||
HttpWebServerTest(SC::TestReport& report) : TestCase(report, "HttpServerTest") | ||
{ | ||
if (test_section("HttpWebServer")) | ||
{ | ||
httpWebServerTest(); | ||
} | ||
} | ||
void httpWebServerTest(); | ||
}; | ||
|
||
void SC::HttpWebServerTest::httpWebServerTest() | ||
{ | ||
// Create a test file in the application root directory | ||
FileSystem fs; | ||
SC_TEST_EXPECT(fs.init(report.applicationRootDirectory)); | ||
SC_TEST_EXPECT(fs.write("file.html", "<html><body>Response from file</body></html>")); | ||
AsyncEventLoop eventLoop; | ||
SC_TEST_EXPECT(eventLoop.create()); | ||
|
||
//! [HttpWebServerSnippet] | ||
// Creates an HttpServer that serves files from application root directory | ||
HttpServer server; | ||
HttpWebServer webServer; | ||
SC_TEST_EXPECT(server.start(eventLoop, 16, "127.0.0.1", 8090)); | ||
SC_TEST_EXPECT(webServer.init(report.applicationRootDirectory)); | ||
|
||
server.onRequest = [&](HttpRequest& req, HttpResponse& res) { webServer.serveFile(req, res); }; | ||
//! [HttpWebServerSnippet] | ||
|
||
struct Context | ||
{ | ||
int numRequests = 0; | ||
HttpWebServer& httpWebServer; | ||
HttpServer& httpServer; | ||
} context = {0, webServer, server}; | ||
|
||
// Create an Http Client request for that file | ||
HttpClient client; | ||
SC_TEST_EXPECT(client.get(eventLoop, "http://localhost:8090/file.html")); | ||
client.callback = [this, &context](HttpClient& result) | ||
{ | ||
context.numRequests++; | ||
SC_TEST_EXPECT(result.getResponse().containsString("Response from file")); | ||
SC_TEST_EXPECT(context.httpServer.stopAsync()); | ||
SC_TEST_EXPECT(context.httpWebServer.stopAsync()); | ||
}; | ||
SC_TEST_EXPECT(eventLoop.run()); | ||
|
||
// Remove the test file | ||
SC_TEST_EXPECT(fs.removeFile("file.html")); | ||
} | ||
|
||
namespace SC | ||
{ | ||
void runHttpWebServerTest(SC::TestReport& report) { HttpWebServerTest test(report); } | ||
} // namespace SC |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters