forked from SerenityOS/serenity
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WebServer: Implement a very basic HTTP server :^)
This server listens on port 8000 and serves HTML files from /www. It's very simple and quite naive, but I think we can start here and build our way to something pretty neat. Work towards SerenityOS#792.
- Loading branch information
1 parent
271bc4b
commit 6c752c1
Showing
8 changed files
with
198 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<!DOCTYPE> | ||
<html> | ||
<head><title>WebServer start page!</title></head> | ||
<body> | ||
<h1>SerenityOS WebServer start page!</h1> | ||
<p> | ||
<b>Congratulations!</b> | ||
</p> | ||
<p> | ||
If you are reading this in a browser, I think we may have | ||
succeeded! Unless of course you just loaded the local file | ||
in your browser (cheater!) :^) | ||
</p> | ||
<p> | ||
Try this <a href="other.html">link</a> to another page! | ||
</p> | ||
</body> | ||
</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,16 @@ | ||
<!DOCTYPE> | ||
<html> | ||
<head><title>WebServer other page!</title></head> | ||
<body> | ||
<h1>SerenityOS WebServer other page!</h1> | ||
<p> | ||
<b>Holy moly!</b> | ||
</p> | ||
<p> | ||
This is not even index.html, how neat is that! :^) | ||
</p> | ||
<p> | ||
Maybe you want to go <a href="index.html">back to index.html</a>? | ||
</p> | ||
</body> | ||
</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
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,101 @@ | ||
#include "Client.h" | ||
#include <AK/FileSystemPath.h> | ||
#include <AK/StringBuilder.h> | ||
#include <LibCore/File.h> | ||
#include <LibCore/HttpRequest.h> | ||
#include <stdio.h> | ||
#include <time.h> | ||
|
||
namespace WebServer { | ||
|
||
Client::Client(NonnullRefPtr<Core::TCPSocket> socket, Core::Object* parent) | ||
: Core::Object(parent) | ||
, m_socket(socket) | ||
{ | ||
} | ||
|
||
void Client::die() | ||
{ | ||
remove_from_parent(); | ||
} | ||
|
||
void Client::start() | ||
{ | ||
m_socket->on_ready_to_read = [this] { | ||
auto raw_request = m_socket->read_all(); | ||
if (raw_request.is_null()) { | ||
die(); | ||
return; | ||
} | ||
|
||
dbg() << "Got raw request: '" << String::copy(raw_request) << "'"; | ||
|
||
handle_request(move(raw_request)); | ||
die(); | ||
}; | ||
} | ||
|
||
void Client::handle_request(ByteBuffer raw_request) | ||
{ | ||
auto request_or_error = Core::HttpRequest::from_raw_request(raw_request); | ||
if (!request_or_error.has_value()) | ||
return; | ||
auto& request = request_or_error.value(); | ||
|
||
dbg() << "Got HTTP request: " << request.method_name() << " " << request.resource(); | ||
for (auto& header : request.headers()) { | ||
dbg() << " " << header.name << " => " << header.value; | ||
} | ||
|
||
if (request.method() != Core::HttpRequest::Method::GET) { | ||
send_error_response(403, "Forbidden, bro!", request); | ||
return; | ||
} | ||
|
||
FileSystemPath canonical_path(request.resource()); | ||
dbg() << "Requested canonical path: '" << canonical_path.string() << "'"; | ||
|
||
StringBuilder path_builder; | ||
path_builder.append("/www/"); | ||
path_builder.append(canonical_path.string()); | ||
|
||
if (Core::File::is_directory(path_builder.to_string())) | ||
path_builder.append("/index.html"); | ||
|
||
auto file = Core::File::construct(path_builder.to_string()); | ||
if (!file->open(Core::File::ReadOnly)) { | ||
send_error_response(404, "Not found, bro!", request); | ||
return; | ||
} | ||
|
||
m_socket->write("HTTP/1.0 200 OK\r\n"); | ||
m_socket->write("Server: WebServer (SerenityOS)\r\n"); | ||
m_socket->write("Content-Type: text/html\r\n"); | ||
m_socket->write("\r\n"); | ||
|
||
m_socket->write(file->read_all()); | ||
|
||
log_response(200, request); | ||
} | ||
|
||
void Client::send_error_response(unsigned code, const StringView& message, const Core::HttpRequest& request) | ||
{ | ||
StringBuilder builder; | ||
builder.appendf("HTTP/1.0 %u", code); | ||
builder.append(message); | ||
builder.append("\r\n\r\n"); | ||
builder.append("<!DOCTYPE html><html><body><h1>"); | ||
builder.appendf("%u ", code); | ||
builder.append(message); | ||
builder.append("</h1></body></html>"); | ||
m_socket->write(builder.to_string()); | ||
|
||
log_response(code, request); | ||
} | ||
|
||
void Client::log_response(unsigned code, const Core::HttpRequest& request) | ||
{ | ||
printf("%lld :: %03u :: %s %s\n", time(nullptr), code, request.method_name().characters(), request.resource().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,28 @@ | ||
#pragma once | ||
|
||
#include <LibCore/Object.h> | ||
#include <LibCore/TCPSocket.h> | ||
|
||
namespace Core { | ||
class HttpRequest; | ||
} | ||
|
||
namespace WebServer { | ||
|
||
class Client final : public Core::Object { | ||
C_OBJECT(Client); | ||
public: | ||
void start(); | ||
|
||
private: | ||
Client(NonnullRefPtr<Core::TCPSocket>, Core::Object* parent); | ||
|
||
void handle_request(ByteBuffer); | ||
void send_error_response(unsigned code, const StringView& message, const Core::HttpRequest&); | ||
void die(); | ||
void log_response(unsigned code, const Core::HttpRequest&); | ||
|
||
NonnullRefPtr<Core::TCPSocket> m_socket; | ||
}; | ||
|
||
} |
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,9 @@ | ||
OBJS = \ | ||
Client.o \ | ||
main.o | ||
|
||
PROGRAM = WebServer | ||
|
||
LIB_DEPS = Core | ||
|
||
include ../../Makefile.common |
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,23 @@ | ||
#include "Client.h" | ||
#include <LibCore/EventLoop.h> | ||
#include <LibCore/TCPServer.h> | ||
|
||
int main(int argc, char** argv) | ||
{ | ||
(void)argc; | ||
(void)argv; | ||
|
||
Core::EventLoop loop; | ||
|
||
auto server = Core::TCPServer::construct(); | ||
|
||
server->on_ready_to_accept = [&] { | ||
auto client_socket = server->accept(); | ||
ASSERT(client_socket); | ||
auto client = WebServer::Client::construct(client_socket.release_nonnull(), server); | ||
client->start(); | ||
}; | ||
|
||
server->listen({}, 8000); | ||
return loop.exec(); | ||
} |