Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions lib/dexcom_client/library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "dexcom_client",
"version": "0.1.0",
"description": "Client library for interacting with Dexcom services.",
"authors": [
{
"name": "SugarSentry Contributor",
"maintainer": true
}
],
"dependencies": {
"bblanchon/ArduinoJson": "^6.18.5"
},
"frameworks": "arduino",
"platforms": "espressif32"
}
16 changes: 16 additions & 0 deletions lib/glucose_parser/library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "glucose_parser",
"version": "0.1.0",
"description": "Parses glucose readings from various formats.",
"authors": [
{
"name": "SugarSentry Contributor",
"maintainer": true
}
],
"dependencies": {
"dexcom_client": "*"
},
"frameworks": "arduino",
"platforms": "espressif32"
}
3 changes: 2 additions & 1 deletion lib/http_client/include/i_http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct HttpResponse
int statusCode;
std::string body;
std::map<std::string, std::string> headers;
int contentLength = 0; // Add this member
};

struct HttpRequest
Expand Down Expand Up @@ -41,4 +42,4 @@ class IHttpClient
const std::map<std::string, std::string> &headers = {}) = 0;
};

#endif // I_HTTP_CLIENT_H
#endif // I_HTTP_CLIENT_H
12 changes: 9 additions & 3 deletions lib/http_client/include/secure_http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
#include "i_secure_client.h"
#include <memory>

// Struct to hold separately parsed HTTP headers and body
struct RawHttpResponse {
std::string headersStr; // Contains status line + all headers
std::string bodyStr; // Contains just the response body
};

class SecureHttpClient : public IHttpClient
{
public:
Expand All @@ -28,9 +34,9 @@ class SecureHttpClient : public IHttpClient
uint16_t _port;
static constexpr uint32_t DEFAULT_TIMEOUT = 5000; // 5 seconds

std::string readResponse();
HttpResponse parseResponse(const std::string &rawResponse);
RawHttpResponse readResponse();
HttpResponse parseResponse(const RawHttpResponse &rawResponse);
void writeHeaders(const std::map<std::string, std::string> &headers);
};

#endif // SECURE_HTTP_CLIENT_H
#endif // SECURE_HTTP_CLIENT_H
183 changes: 102 additions & 81 deletions lib/http_client/src/secure_http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,75 @@ HttpResponse SecureHttpClient::send(const HttpRequest &request)
}

// Write request line
DEBUG_PRINTF(">> %s %s HTTP/1.1\n", request.method.c_str(), request.url.c_str());
_client->println(request.method + " " + request.url + " HTTP/1.1");

// Write headers
DEBUG_PRINTF(">> Host: %s\n", _host.c_str());
_client->println("Host: " + _host);
writeHeaders(request.headers);

// Write body if present
if (request.body)
{
DEBUG_PRINTF(">> Content-Length: %zu\n", request.body->length());
_client->println("Content-Length: " + std::to_string(request.body->length()));
DEBUG_PRINT(">> [blank line]");
_client->println();
DEBUG_PRINTF(">> Body:\n%s\n", request.body->c_str());
_client->println(*request.body);
}
else
{
DEBUG_PRINT(">> [blank line]");
_client->println();
}

return parseResponse(readResponse());
RawHttpResponse rawResponse = readResponse(); // Reads only headers now
HttpResponse response = parseResponse(rawResponse); // Parses headers, gets contentLength

// Read the body based on Content-Length
response.body.clear(); // Ensure body starts empty
if (response.contentLength > 0) {
response.body.reserve(response.contentLength);
size_t bytesRead = 0;

// Read exactly contentLength bytes, no more no less
while (bytesRead < response.contentLength && _client->connected()) {
// Wait for data to be available
while (_client->available() == 0 && _client->connected()) {
// Optional: Add a small delay or timeout mechanism here
PLATFORM_DELAY(1); // Small delay to wait for data
}

// Read a character when available
if (_client->available() > 0) {
int c = _client->read();

if (c < 0) {
break; // Error reading
}

response.body += (char)c;
bytesRead++;
} else {
// Connection closed or timeout before full body read
DEBUG_PRINT("Error reading response body: connection issue or timeout");
// Optionally set an error status code or return partial body
response.statusCode = 504; // Gateway Timeout (example)
break;
}
}
} else if (response.contentLength == 0) {
// Body is intentionally empty
} else { // Content-Length was missing or invalid
// Read remaining data based on availability (less reliable)
while (_client->available() > 0) {
response.body += (char)_client->read();
}
}

return response;
}

HttpResponse SecureHttpClient::get(const std::string &url,
Expand All @@ -88,125 +138,96 @@ HttpResponse SecureHttpClient::post(const std::string &url,
return send(request);
}

std::string SecureHttpClient::readResponse()
RawHttpResponse SecureHttpClient::readResponse()
{
std::string response;
bool headers_complete = false;
RawHttpResponse response;

// Read headers
while (_client->connected() && !headers_complete)
// Read headers line by line
while (_client->connected())
{
std::string line = _client->readStringUntil('\n');
response += line;
response.headersStr += line;

// Check for empty line that separates headers from body
if (line == "\r" || line == "\r\n")
{
headers_complete = true;
break; // Headers complete, don't read the body
}

// Prevent infinite loop if no proper header termination
if (response.length() > 16384)
{ // 16KB max header size
if (response.headersStr.length() > 8192)
{ // 8KB max header size
break;
}
}

// Read body if there's data available
int content_length = 0;
auto it = response.find("Content-Length: ");
if (it != std::string::npos)
{
size_t end = response.find("\r\n", it);
if (end != std::string::npos)
{
content_length = std::stoi(response.substr(it + 16, end - (it + 16)));
}
}

// Read exact content length if specified
if (content_length > 0)
{
while (_client->available() && content_length > 0)
{
char c = static_cast<char>(_client->read());
response += c;
content_length--;
}
}
else
{
// Read any remaining data
while (_client->available())
{
char c = static_cast<char>(_client->read());
response += c;
}
}

// Leave bodyStr empty - don't attempt to read the body

return response;
}

HttpResponse SecureHttpClient::parseResponse(const std::string &rawResponse)
HttpResponse SecureHttpClient::parseResponse(const RawHttpResponse &rawResponse)
{
HttpResponse response;
std::istringstream responseStream(rawResponse);
std::istringstream headersStream(rawResponse.headersStr);
std::string line;

// Parse status line
if (std::getline(responseStream, line))
{
if (line.length() > 12)
{
// Parse Status Line
if (std::getline(headersStream, line) && line.length() > 12) {
// Remove trailing \r if present
if (!line.empty() && line.back() == '\r') line.pop_back();
try {
response.statusCode = std::stoi(line.substr(9, 3));
} catch (...) {
response.statusCode = 500; // Error parsing status code
}
else
{
response.statusCode = 500;
}
} else {
response.statusCode = 500; // Malformed status line
}

// Parse headers
while (std::getline(responseStream, line) && line != "\r")
{
size_t colonPos = line.find(':');
if (colonPos != std::string::npos)
{
std::string key = line.substr(0, colonPos);
std::string value = line.substr(colonPos + 2); // Skip ": "
if (!value.empty() && value.back() == '\r')
{
value.pop_back();
// Parse Headers
while (std::getline(headersStream, line)) {
// Remove trailing \r if present
if (!line.empty() && line.back() == '\r') line.pop_back();

// Stop if we hit the empty line separating headers from body
if (line.empty()) break;

size_t colon_pos = line.find(':');
if (colon_pos != std::string::npos) {
std::string key = line.substr(0, colon_pos);
size_t value_start = colon_pos + 1;
// Trim leading whitespace from value
while (value_start < line.length() && isspace(line[value_start])) {
value_start++;
}
std::string value = line.substr(value_start);

// Store header
response.headers[key] = value;
}
}

// Parse body
std::string body;
while (std::getline(responseStream, line))
{
body += line;
if (!responseStream.eof())
{
body += "\n";
// Check for Content-Length
if (key == "Content-Length") {
try {
response.contentLength = std::stoi(value);
} catch(...) {
response.contentLength = 0; // Error parsing length
}
}
}
}

// Trim trailing whitespace from body
while (!body.empty() && (body.back() == '\n' || body.back() == '\r' || body.back() == ' '))
{
body.pop_back();
}
// Body is NOT parsed here
response.body = "";

response.body = body;
return response;
}

void SecureHttpClient::writeHeaders(const std::map<std::string, std::string> &headers)
{
for (const auto &[key, value] : headers)
{
DEBUG_PRINTF(">> %s: %s\n", key.c_str(), value.c_str());
_client->println(key + ": " + value);
}
}
}
8 changes: 8 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ test_ignore = test_desktop

[env:native]
platform = native
build_src_filter = +<*> -<src/>
build_unflags = -std=gnu++11
lib_ldf_mode = deep+
lib_compat_mode = off
build_src_flags =
-DUNIT_TEST
-I lib/json_parser/src
-I lib/dexcom_client/src
-I lib/glucose_parser/src
build_flags =
-std=gnu++17
-I test/test_desktop/mocks
Expand Down
36 changes: 36 additions & 0 deletions task_history.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
Task 45

Refactor `readResponse` to Read Only Headers

I've successfully refactored the SecureHttpClient::parseResponse method to work with the updated readResponse method from the previous task. The changes include:

Added a contentLength field to the HttpResponse struct in i_http_client.h to store Content-Length header value
Completely refactored the parseResponse method to:
Use std::istringstream to process headers line-by-line
Extract the status code from the first line
Parse headers including the Content-Length value when present
Leave the response body empty (as specified in the task)
Updated all the tests in test_secure_http_client.cpp to verify:
The status code is correctly parsed
Headers are extracted properly
Content-Length is stored in the new field
The body remains empty

----

Task 44

refactor: Improve HTTP request logging in SecureHttpClient

----

Task 43

refactor: Separate status line and headers parsing in SecureHttpClient response handling

----

Task 42

Refactored SecureHttpClient::readResponse to separate HTTP headers and body processing. The method now returns a RawHttpResponse struct containing distinct headersStr and bodyStr fields instead of a combined string. This separation maintains the original header parsing approach (line-by-line) that worked in the previous implementation, while ensuring the body can be accurately extracted after the header/body boundary. The parseResponse method was updated to work with this new structure, allowing for more precise HTTP response handling. Tests failing for the time being.

Task 41

Removed unused static buffer size constant and JsonDocument type alias from ArduinoJsonParser header.
Expand Down
Loading