blob: cc29c6e16d25b52b95e3fd4e6e6faaf951f8c9fa [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include <https/HTTPClientConnection.h>
#include <https/HTTPRequestResponse.h>
#include <https/PlainSocket.h>
#include <https/RunLoop.h>
#include <https/SafeCallbackable.h>
#include <https/SSLSocket.h>
#include <https/Support.h>
#include <glog/logging.h>
#include <arpa/inet.h>
#include <cerrno>
#include <netinet/in.h>
#include <unistd.h>
using namespace android;
HTTPClientConnection::HTTPClientConnection(
std::shared_ptr<RunLoop> rl,
std::shared_ptr<WebSocketHandler> webSocketHandler,
std::string_view path,
ServerSocket::TransportType transportType,
const std::optional<std::string> &trusted_pem_path)
: mInitCheck(-ENODEV),
mRunLoop(rl),
mWebSocketHandler(webSocketHandler),
mPath(path),
mTransportType(transportType),
mSendPending(false),
mInBufferLen(0),
mWebSocketMode(false) {
int sock;
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock < 0) {
mInitCheck = -errno;
goto bail;
}
makeFdNonblocking(sock);
if (mTransportType == ServerSocket::TransportType::TLS) {
CHECK(trusted_pem_path.has_value());
mImpl = std::make_shared<SSLSocket>(
mRunLoop, sock, 0 /* flags */, *trusted_pem_path);
} else {
mImpl = std::make_shared<PlainSocket>(mRunLoop, sock);
}
mInitCheck = 0;
return;
bail:
;
}
int HTTPClientConnection::initCheck() const {
return mInitCheck;
}
int HTTPClientConnection::connect(const char *host, uint16_t port) {
if (mInitCheck < 0) {
return mInitCheck;
}
sockaddr_in addr;
memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(host);
mRemoteAddr = addr;
int res = ::connect(
mImpl->fd(), reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
if (res < 0 && errno != EINPROGRESS) {
return -errno;
}
mImpl->postSend(makeSafeCallback(this, &HTTPClientConnection::sendRequest));
return 0;
}
void HTTPClientConnection::sendRequest() {
std::string request;
request =
"GET " + mPath + " HTTP/1.1\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: foobar\r\n"
"\r\n";
CHECK(mRunLoop->isCurrentThread());
std::copy(request.begin(), request.end(), std::back_inserter(mOutBuffer));
if (!mSendPending) {
mSendPending = true;
mImpl->postSend(
makeSafeCallback(this, &HTTPClientConnection::sendOutputData));
}
mImpl->postRecv(
makeSafeCallback(this, &HTTPClientConnection::receiveResponse));
}
void HTTPClientConnection::receiveResponse() {
mInBuffer.resize(mInBufferLen + 1024);
ssize_t n;
do {
n = mImpl->recv(mInBuffer.data() + mInBufferLen, 1024);
} while (n < 0 && errno == EINTR);
if (n == 0) {
(void)handleResponse(true /* isEOS */);
return;
} else if (n < 0) {
LOG(ERROR) << "recv returned error '" << strerror(errno) << "'.";
return;
}
mInBufferLen += static_cast<size_t>(n);
if (!handleResponse(false /* isEOS */)) {
mImpl->postRecv(
makeSafeCallback(this, &HTTPClientConnection::receiveResponse));
}
}
bool HTTPClientConnection::handleResponse(bool isEOS) {
if (mWebSocketMode) {
ssize_t n = mWebSocketHandler->handleRequest(
mInBuffer.data(), mInBufferLen, isEOS);
if (n > 0) {
mInBuffer.erase(mInBuffer.begin(), mInBuffer.begin() + n);
mInBufferLen -= n;
}
return n <= 0;
}
size_t len = mInBufferLen;
if (!isEOS) {
static const char kPattern[] = "\r\n\r\n";
// Don't count the trailing NUL.
static constexpr size_t kPatternLength = sizeof(kPattern) - 1;
size_t i = 0;
while (i + kPatternLength <= mInBufferLen
&& memcmp(mInBuffer.data() + i, kPattern, kPatternLength)) {
++i;
}
if (i + kPatternLength > mInBufferLen) {
return false;
}
// Found a match.
len = i + kPatternLength;
}
HTTPResponse response;
if (response.setTo(mInBuffer.data(), len) < 0) {
LOG(ERROR) << "failed to get valid server response.";
mInBuffer.clear();
mInBufferLen = 0;
return true;
} else {
LOG(INFO)
<< "got response: "
<< response.getVersion()
<< ", "
<< response.getStatusCode()
<< ", "
<< response.getStatusMessage();
hexdump(mInBuffer.data(), len);
mInBuffer.erase(mInBuffer.begin(), mInBuffer.begin() + len);
mInBufferLen -= len;
size_t contentLength = response.getContentLength();
LOG(VERBOSE) << "contentLength = " << contentLength;
assert(mInBufferLen >= contentLength);
hexdump(mInBuffer.data(), contentLength);
mInBuffer.clear();
if (response.getStatusCode() == 101) {
mWebSocketMode = true;
mWebSocketHandler->setOutputCallback(
mRemoteAddr,
[this](const uint8_t *data, size_t size) {
queueOutputData(data, size);
});
const std::string msg = "\"message\":\"Hellow, world!\"";
mWebSocketHandler->sendMessage(msg.c_str(), msg.size());
return false;
}
}
return true;
}
void HTTPClientConnection::queueOutputData(const uint8_t *data, size_t size) {
CHECK(mRunLoop->isCurrentThread());
std::copy(data, &data[size], std::back_inserter(mOutBuffer));
if (!mSendPending) {
mSendPending = true;
mImpl->postSend(
makeSafeCallback(this, &HTTPClientConnection::sendOutputData));
}
}
void HTTPClientConnection::sendOutputData() {
mSendPending = false;
const size_t size = mOutBuffer.size();
size_t offset = 0;
while (offset < size) {
ssize_t n = mImpl->send(mOutBuffer.data() + offset, size - offset);
if (n < 0) {
if (errno == EINTR) {
continue;
}
if (errno == EAGAIN) {
break;
}
// The remote is gone (due to error), clear the output buffer and disconnect.
offset = size;
break;
} else if (n == 0) {
// The remote seems gone, clear the output buffer and disconnect.
offset = size;
break;
}
offset += static_cast<size_t>(n);
}
mOutBuffer.erase(mOutBuffer.begin(), mOutBuffer.begin() + offset);
if (!mOutBuffer.empty()) {
mSendPending = true;
mImpl->postSend(
makeSafeCallback(this, &HTTPClientConnection::sendOutputData));
return;
}
}