ladybird/Libraries/LibCore/CHttpJob.cpp
Andreas Kling bdf23a3d23 LibCore: Make it possible to cancel pending CNetworkJobs
Subclasses of CNetworkJob handle this by overriding shutdown().
This patch implements it for CHttpJob by simply tearing down the
underlying socket.

We also automatically call shutdown() after the job finishes,
regardless of success or failure. :^)
2019-09-21 17:32:26 +02:00

138 lines
4.9 KiB
C++

#include <LibCore/CHttpJob.h>
#include <LibCore/CHttpResponse.h>
#include <LibCore/CTCPSocket.h>
#include <stdio.h>
#include <unistd.h>
CHttpJob::CHttpJob(const CHttpRequest& request)
: m_request(request)
{
}
CHttpJob::~CHttpJob()
{
}
void CHttpJob::on_socket_connected()
{
auto raw_request = m_request.to_raw_request();
#if 0
dbg() << "CHttpJob: raw_request:";
dbg() << String::copy(raw_request).characters();
#endif
bool success = m_socket->send(raw_request);
if (!success)
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::TransmissionFailed); });
m_socket->on_ready_to_read = [&] {
if (is_cancelled())
return;
if (m_state == State::InStatus) {
if (!m_socket->can_read_line())
return;
auto line = m_socket->read_line(PAGE_SIZE);
if (line.is_null()) {
fprintf(stderr, "CHttpJob: Expected HTTP status\n");
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::TransmissionFailed); });
}
auto parts = String::copy(line, Chomp).split(' ');
if (parts.size() < 3) {
fprintf(stderr, "CHttpJob: Expected 3-part HTTP status, got '%s'\n", line.pointer());
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
bool ok;
m_code = parts[1].to_uint(ok);
if (!ok) {
fprintf(stderr, "CHttpJob: Expected numeric HTTP status\n");
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
m_state = State::InHeaders;
return;
}
if (m_state == State::InHeaders) {
if (!m_socket->can_read_line())
return;
auto line = m_socket->read_line(PAGE_SIZE);
if (line.is_null()) {
fprintf(stderr, "CHttpJob: Expected HTTP header\n");
return did_fail(CNetworkJob::Error::ProtocolFailed);
}
auto chomped_line = String::copy(line, Chomp);
if (chomped_line.is_empty()) {
m_state = State::InBody;
return;
}
auto parts = chomped_line.split(':');
if (parts.is_empty()) {
fprintf(stderr, "CHttpJob: Expected HTTP header with key/value\n");
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
auto name = parts[0];
if (chomped_line.length() < name.length() + 2) {
fprintf(stderr, "CHttpJob: Malformed HTTP header: '%s' (%d)\n", chomped_line.characters(), chomped_line.length());
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
auto value = chomped_line.substring(name.length() + 2, chomped_line.length() - name.length() - 2);
m_headers.set(name, value);
dbg() << "CHttpJob: [" << name << "] = '" << value << "'";
return;
}
ASSERT(m_state == State::InBody);
ASSERT(m_socket->can_read());
auto payload = m_socket->receive(PAGE_SIZE);
if (!payload) {
if (m_socket->eof())
return finish_up();
return deferred_invoke([this](auto&) { did_fail(CNetworkJob::Error::ProtocolFailed); });
}
m_received_buffers.append(payload);
m_received_size += payload.size();
auto content_length_header = m_headers.get("Content-Length");
if (content_length_header.has_value()) {
bool ok;
if (m_received_size >= content_length_header.value().to_uint(ok) && ok)
return finish_up();
}
};
}
void CHttpJob::finish_up()
{
m_state = State::Finished;
auto flattened_buffer = ByteBuffer::create_uninitialized(m_received_size);
u8* flat_ptr = flattened_buffer.data();
for (auto& received_buffer : m_received_buffers) {
memcpy(flat_ptr, received_buffer.data(), received_buffer.size());
flat_ptr += received_buffer.size();
}
m_received_buffers.clear();
auto response = CHttpResponse::create(m_code, move(m_headers), move(flattened_buffer));
deferred_invoke([this, response](auto&) {
did_finish(move(response));
});
}
void CHttpJob::start()
{
ASSERT(!m_socket);
m_socket = CTCPSocket::construct(this);
m_socket->on_connected = [this] {
dbg() << "CHttpJob: on_connected callback";
on_socket_connected();
};
bool success = m_socket->connect(m_request.url().host(), m_request.url().port());
if (!success)
return did_fail(CNetworkJob::Error::ConnectionFailed);
}
void CHttpJob::shutdown()
{
if (!m_socket)
return;
m_socket->on_ready_to_read = nullptr;
m_socket->on_connected = nullptr;
m_socket = nullptr;
}