demo_example/http/coroutine_http/connection.hpp (126 lines of code) (raw):
/*
* Copyright (c) 2022, Alibaba Group Holding Limited;
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ASYNC_SIMPLE_CONNECTION_HPP
#define ASYNC_SIMPLE_CONNECTION_HPP
#include <fstream>
#include "../../asio_coro_util.hpp"
#include "../http_request.hpp"
#include "../http_response.hpp"
class connection {
public:
connection(asio::ip::tcp::socket socket, std::string &&doc_root)
: socket_(std::move(socket)), doc_root_(std::move(doc_root)) {}
~connection() {
std::error_code ec;
socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
socket_.close(ec);
}
async_simple::coro::Lazy<void> start() {
for (;;) {
auto [error, bytes_transferred] =
co_await async_read_some(socket_, asio::buffer(read_buf_));
if (error) {
std::cout << "error: " << error.message()
<< ", size=" << bytes_transferred << '\n';
break;
}
request_parser::result_type result;
std::tie(result, std::ignore) = parser_.parse(
request_, read_buf_, read_buf_ + bytes_transferred);
if (result == request_parser::good) {
handle_request(request_, response_);
co_await async_write(socket_, response_.to_buffers());
bool keep_alive = is_keep_alive();
if (!keep_alive) {
break;
}
request_ = {};
response_ = {};
parser_.reset();
} else if (result == request_parser::bad) {
response_ = build_response(status_type::bad_request);
co_await async_write(socket_, response_.to_buffers());
break;
}
}
}
private:
void handle_request(const request &req, response &rep) {
// Decode url to path.
std::string request_path;
if (!url_decode(req.uri, request_path)) {
rep = build_response(status_type::bad_request);
return;
}
// Request path must be absolute and not contain "..".
if (request_path.empty() || request_path[0] != '/' ||
request_path.find("..") != std::string::npos) {
rep = build_response(status_type::bad_request);
return;
}
// If path ends in slash (i.e. is a directory) then add "index.html".
if (request_path[request_path.size() - 1] == '/') {
rep = build_response(status_type::ok);
return;
}
// Determine the file extension.
std::size_t last_slash_pos = request_path.find_last_of("/");
std::size_t last_dot_pos = request_path.find_last_of(".");
std::string extension;
if (last_dot_pos != std::string::npos &&
last_dot_pos > last_slash_pos) {
extension = request_path.substr(last_dot_pos + 1);
}
// Open the file to send back.
std::string full_path = doc_root_ + request_path;
std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
if (!is) {
rep = build_response(status_type::not_found);
return;
}
// Fill out the response to be sent to the client.
rep.status = status_type::ok;
char buf[512];
while (is.read(buf, sizeof(buf)).gcount() > 0)
rep.content.append(buf, is.gcount());
rep.headers.resize(2);
rep.headers[0].name = "Content-Length";
rep.headers[0].value = std::to_string(rep.content.size());
rep.headers[1].name = "Content-Type";
rep.headers[1].value = mime_types::extension_to_type(extension);
}
bool url_decode(const std::string &in, std::string &out) {
out.clear();
out.reserve(in.size());
for (std::size_t i = 0; i < in.size(); ++i) {
if (in[i] == '%') {
if (i + 3 <= in.size()) {
int value = 0;
std::istringstream is(in.substr(i + 1, 2));
if (is >> std::hex >> value) {
out += static_cast<char>(value);
i += 2;
} else {
return false;
}
} else {
return false;
}
} else if (in[i] == '+') {
out += ' ';
} else {
out += in[i];
}
}
return true;
}
bool is_keep_alive() {
for (auto &[k, v] : request_.headers) {
if (k == "Connection" && v == "close") {
return false;
break;
}
}
return true;
}
private:
asio::ip::tcp::socket socket_;
char read_buf_[1024];
request_parser parser_;
request request_;
response response_;
std::string doc_root_;
};
#endif