Skip to content

Commit 4eeaa74

Browse files
committed
feat: add UploadHandler
1 parent dc8e2d6 commit 4eeaa74

File tree

10 files changed

+305
-4
lines changed

10 files changed

+305
-4
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ srcs = \
3737
src/handler/handler.hpp \
3838
src/handler/static_file_handler.cpp \
3939
src/handler/static_file_handler.hpp \
40+
src/handler/upload_handler.cpp \
41+
src/handler/upload_handler.hpp \
4042
src/http/http_parser.cpp \
4143
src/http/http_parser.hpp \
4244
src/http/http_request.cpp \
@@ -62,6 +64,7 @@ deps = $(objs:.o=.d)
6264

6365
# Test sources (keep in alphabetical order)
6466
test_srcs = \
67+
tests/upload_handler_unittest.cpp \
6568
tests/error_handler_unittest.cpp \
6669
tests/http_parser_unittest.cpp \
6770
tests/http_response_unittest.cpp \

src/config/server_config.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ ServerConfig make_site1_config()
3434
cfg.default_location.path = "";
3535
cfg.default_location.index = "index.html";
3636

37+
// ------------------------
38+
// Upload location
39+
// ------------------------
40+
cfg.upload_location.path = "/upload";
41+
cfg.upload_location.index = "index.html";
42+
3743
// ------------------------
3844
// Other locations
3945
// ------------------------

src/config/server_config.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct ServerConfig {
2525
std::vector<std::string> methods; // allowed methods
2626

2727
Location default_location;
28+
Location upload_location;
2829
std::vector<Location> locations;
2930
};
3031

src/handler/error_handler.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ size_t ErrorHandler::read_data(char* buf, size_t n)
99
{
1010
std::string res = res_.to_string();
1111
LOG(DEBUG) << res;
12-
std::memcpy(buf, &res, n);
13-
return (res.size());
12+
size_t to_copy = std::min(res.size(), n);
13+
std::memcpy(buf, res.data(), to_copy);
14+
return (to_copy);
1415
}
1516

1617
// We never write to this handler (read-only)

src/handler/upload_handler.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#include "handler/upload_handler.hpp"
2+
3+
#include "http/http_response.hpp"
4+
#include "util/log_message.hpp"
5+
#include "util/syscall_error.hpp"
6+
7+
#include <errno.h>
8+
#include <fcntl.h>
9+
#include <unistd.h>
10+
11+
#include <cstring>
12+
#include <iostream>
13+
#include <sstream>
14+
15+
/*
16+
ERRORS :
17+
insufficient space → 507 Insufficient Storage
18+
19+
forbidden directory → 403
20+
21+
invalid request format → 400
22+
23+
If file exists → POST = 409 Conflict
24+
25+
26+
Also, the UploadHandler could handle, both POST and PUT requests
27+
28+
*/
29+
30+
UploadHandler::UploadHandler(const std::string& path, size_t content_length)
31+
: file_path_(path),
32+
fd_(-1),
33+
bytes_written_(0),
34+
content_length_(content_length),
35+
eob_reached_(false),
36+
headers_off_(0)
37+
{
38+
struct stat file_stat;
39+
if (stat(path.c_str(), &file_stat) == 0 && S_ISREG(file_stat.st_mode)) {
40+
// file already exists
41+
HttpResponse res;
42+
res.code = HttpResponse::kStatusConflict; // 409
43+
res.inline_body = "<h1> 409 Conflict — File already exists </h1>";
44+
headers_ = res.to_string();
45+
return;
46+
}
47+
fd_ = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
48+
if (fd_ == -1) {
49+
HttpResponse res;
50+
res.code = HttpResponse::kStatusDiskFull;
51+
res.inline_body = "<h1> 507 Disk Full </h1>"; // to display a message during testing
52+
headers_ = res.to_string();
53+
return;
54+
}
55+
}
56+
57+
UploadHandler::~UploadHandler()
58+
{
59+
if (fd_ != -1)
60+
close(fd_);
61+
}
62+
63+
bool UploadHandler::needs_input() const
64+
{
65+
return bytes_written_ < content_length_;
66+
}
67+
68+
bool UploadHandler::has_output() const
69+
{
70+
return headers_off_ < headers_.size();
71+
}
72+
73+
size_t UploadHandler::read_data(char* buf, size_t n)
74+
{
75+
// LOG(DEBUG) << "inside UploadHandler::read_data -> headers_.size == " << headers_.size();
76+
size_t to_copy = std::min(headers_.size() - headers_off_, n);
77+
std::memcpy(buf, headers_.c_str() + headers_off_, to_copy);
78+
headers_off_ += to_copy;
79+
return (to_copy);
80+
}
81+
82+
size_t UploadHandler::write_data(const char* buf, size_t n)
83+
{
84+
ssize_t bytes = write(fd_, buf, n);
85+
if (bytes == -1) {
86+
// error occured
87+
eob_reached_ = true;
88+
if (headers_.empty()) {
89+
HttpResponse res;
90+
res.code = HttpResponse::kStatusDiskFull;
91+
res.inline_body = "<h1> 507 Disk Full </h1>"; // to display a message during testing
92+
headers_ = res.to_string();
93+
bytes_written_ = content_length_; // to ensure needs_input returns false
94+
}
95+
return 0;
96+
}
97+
bytes_written_ += bytes;
98+
if (bytes_written_ < content_length_) {
99+
100+
return (bytes);
101+
}
102+
103+
if (headers_.empty()) {
104+
eob_reached_ = true;
105+
HttpResponse res;
106+
res.code = HttpResponse::kStatusCreated;
107+
res.inline_body = "<h1> 201 File Created </h1>"; // to display a message during testing
108+
headers_ = res.to_string();
109+
}
110+
return (0);
111+
}

src/handler/upload_handler.hpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#ifndef HANDLER_UPLOAD_HANDLER_HPP_
2+
#define HANDLER_UPLOAD_HANDLER_HPP_
3+
4+
#include "handler/handler.hpp"
5+
6+
#include <sys/stat.h>
7+
8+
#include <string>
9+
10+
class UploadHandler : public Handler {
11+
public:
12+
explicit UploadHandler(const std::string& path, size_t content_lenght);
13+
virtual ~UploadHandler();
14+
15+
virtual size_t read_data(char* buf, size_t n); // we should not read from this handler
16+
virtual size_t write_data(const char* buf, size_t n);
17+
18+
virtual bool has_output() const;
19+
virtual bool needs_input() const;
20+
virtual bool is_done() const { return headers_sent(); }
21+
const std::string& path() const { return file_path_; }
22+
23+
private:
24+
UploadHandler(const UploadHandler&);
25+
UploadHandler& operator=(const UploadHandler&);
26+
27+
bool headers_sent() const { return headers_off_ == headers_.size(); }
28+
bool body_written() const { return eob_reached_; }
29+
30+
const std::string file_path_;
31+
32+
int fd_;
33+
size_t bytes_written_;
34+
size_t content_length_;
35+
bool eob_reached_;
36+
std::string headers_;
37+
size_t headers_off_;
38+
};
39+
40+
#endif

src/http/http_response.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,14 @@ const char* HttpResponse::reason_phrase(HttpResponse::Status code) const
4545
{
4646
switch (code) {
4747
case kStatusOk: return "OK";
48+
case kStatusCreated: return "File Created";
4849
case kStatusBadRequest: return "Bad Request";
4950
case kStatusForbidden: return "Forbidden";
5051
case kStatusNotFound: return "Not Found";
5152
case kStatusMethodNotAllowed: return "Method Not Allowed";
53+
case kStatusConflict: return "Conflict";
5254
case kStatusInternalServerError: return "Internal Server Error";
5355
case kStatusNotImplemented: return "Not Implemented";
56+
case kStatusDiskFull: return "Disk Full";
5457
}
5558
}

src/http/http_response.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ struct HttpResponse {
77
public:
88
enum Status {
99
kStatusOk = 200,
10+
kStatusCreated = 201,
1011
kStatusBadRequest = 400,
1112
kStatusForbidden = 403,
1213
kStatusNotFound = 404,
1314
kStatusMethodNotAllowed = 405,
15+
kStatusConflict = 409,
1416
kStatusInternalServerError = 500,
15-
kStatusNotImplemented = 501
17+
kStatusNotImplemented = 501,
18+
kStatusDiskFull = 507
1619
};
1720

1821
HttpResponse();

src/router/router.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "config/server_config.hpp"
44
#include "handler/error_handler.hpp"
55
#include "handler/static_file_handler.hpp"
6+
#include "handler/upload_handler.hpp"
67
#include "http/http_request.hpp"
78
#include "http/http_response.hpp"
89
#include "util/log_message.hpp"
@@ -122,7 +123,13 @@ Handler* Router::handle_request(const HttpRequest& request)
122123
if (request.method == "GET") {
123124
return new StaticFileHandler(full_path);
124125
}
126+
if (request.method == "POST") {
127+
return new UploadHandler(full_path, request.content_length);
128+
}
129+
/* if (request.method == "DELETE") {
130+
return new DeleteHandler(full_path);*/
125131

126-
NOTREACHED();
132+
// Fallback, should never happen in theory
133+
std::cerr << "Something terrible happened in the router" << std::endl;
127134
return new ErrorHandler(HttpResponse::kStatusInternalServerError); // 500
128135
}

tests/upload_handler_unittest.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
2+
#include "handler/upload_handler.hpp"
3+
#include "http/http_response.hpp"
4+
#include "stdio.h"
5+
#include "utest/utest.h"
6+
7+
#include <iostream>
8+
9+
static void write_all(Handler* handler, const std::string& content)
10+
{
11+
size_t written = 0;
12+
13+
while (handler->needs_input()) {
14+
size_t n = handler->write_data(content.c_str() + written, content.size() - written);
15+
written += n;
16+
17+
if ((n == 0 && handler->needs_input()))
18+
break;
19+
}
20+
}
21+
22+
UTEST(UploadHandlerTest, needs_input_upload_ongoing)
23+
{
24+
std::string upload_path = "www/example/needs_input_upload_ongoing.txt";
25+
std::string content = "some content to be written to the file";
26+
Handler* handler = new UploadHandler(upload_path, content.size());
27+
ASSERT_TRUE(handler->needs_input());
28+
delete (handler);
29+
remove(upload_path.c_str());
30+
}
31+
32+
UTEST(UploadHandlerTest, has_output_upload_ongoing)
33+
{
34+
std::string upload_path = "www/example/has_output_upload_ongoing.txt";
35+
std::string content = "some content to be written to the file";
36+
Handler* handler = new UploadHandler(upload_path, content.size());
37+
ASSERT_FALSE(handler->has_output());
38+
delete (handler);
39+
remove(upload_path.c_str());
40+
}
41+
42+
UTEST(UploadHandlerTest, needs_input_upload_done)
43+
{
44+
std::string upload_path = "www/example/needs_input_upload_done.txt";
45+
std::string content = "some content to be written to the file";
46+
Handler* handler = new UploadHandler(upload_path, content.size());
47+
write_all(handler, content);
48+
ASSERT_TRUE(handler->has_output());
49+
delete (handler);
50+
remove(upload_path.c_str());
51+
}
52+
53+
UTEST(UploadHandlerTest, has_output_upload_done)
54+
{
55+
std::string upload_path = "www/example/has_output_upload_done.txt";
56+
std::string content = "some content to be written to the file";
57+
Handler* handler = new UploadHandler(upload_path, content.size());
58+
write_all(handler, content);
59+
ASSERT_TRUE(handler->has_output());
60+
delete (handler);
61+
remove(upload_path.c_str());
62+
}
63+
64+
UTEST(UploadHandlerTest, check_content_uploaded)
65+
{
66+
std::string upload_path = "www/example/check_content_uploaded.txt";
67+
std::string content = "some content to be written to the file";
68+
Handler* handler = new UploadHandler(upload_path, content.size());
69+
write_all(handler, content);
70+
ASSERT_FALSE(handler->needs_input());
71+
ASSERT_TRUE(handler->has_output());
72+
delete (handler);
73+
74+
// validating content
75+
FILE* f = fopen(upload_path.c_str(), "r");
76+
std::cout << "opening the file" << std::endl;
77+
ASSERT_TRUE(f != NULL);
78+
char buffer[1028];
79+
size_t n = fread(buffer, 1, sizeof(buffer), f);
80+
fclose(f);
81+
std::string file_content(buffer, n);
82+
std::cout << "file_content = " << file_content << std::endl;
83+
ASSERT_STREQ(content.c_str(), file_content.c_str());
84+
remove(upload_path.c_str());
85+
}
86+
87+
UTEST(UploadHandlerTest, ReturnsUploadSuccess)
88+
{
89+
std::string upload_path = "www/example/return_201.txt";
90+
std::string content = "some content to be written to the file";
91+
Handler* handler = new UploadHandler(upload_path, content.size());
92+
ASSERT_FALSE(handler->has_output());
93+
write_all(handler, content);
94+
ASSERT_TRUE(handler->has_output());
95+
char buffer[1028];
96+
size_t n = handler->read_data(buffer, sizeof(buffer));
97+
std::string http_res(buffer, n);
98+
ASSERT_TRUE(http_res.find("201") != std::string::npos);
99+
delete (handler);
100+
remove(upload_path.c_str());
101+
}
102+
103+
UTEST(UploadHandlerTest, ReturnsFileAlreadyExists)
104+
{
105+
std::string upload_path = "www/example/return_409.txt";
106+
std::string content = "some content to be written to the file";
107+
{
108+
Handler* handler1 = new UploadHandler(upload_path, content.size());
109+
write_all(handler1, content);
110+
ASSERT_TRUE(handler1->has_output());
111+
delete handler1;
112+
}
113+
Handler* handler2 = new UploadHandler(upload_path, content.size());
114+
ASSERT_TRUE(handler2->has_output());
115+
char buffer[1028];
116+
size_t n = handler2->read_data(buffer, sizeof(buffer));
117+
std::string http_res(buffer, n);
118+
ASSERT_TRUE(http_res.find("409") != std::string::npos);
119+
delete (handler2);
120+
remove(upload_path.c_str());
121+
}
122+
123+
UTEST(UploadHandlerTest, return_507)
124+
{
125+
UTEST_SKIP("TODO: Test Disk full without making your machine explode");
126+
}

0 commit comments

Comments
 (0)