Stairs/src/server/geocode.cpp

200 lines
4.4 KiB
C++

/*
* Stairs
* Copyright 2017 (c) Mark van Renswoude
*
* https://git.x2software.net/pub/Stairs
*/
#include "geocode.h"
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include "../debug.h"
#include "../secret.h"
struct ResponseBuffer
{
void* data;
size_t length;
ResponseBuffer* next;
};
String urlencode(const String url)
{
String encoded;
for (int i = 0; i < url.length(); i++)
{
char c = url.charAt(i);
if (c == 0x20)
encoded += "%20";
else if (isalnum(c))
encoded += c;
else
{
encoded += "%";
if (c < 0x10) encoded += "0";
encoded += String(c, HEX);
}
}
return encoded;
}
void memcpy_unaligned(void* dest, void* source, size_t length)
{
uint8_t* destChar = (uint8_t*)dest;
uint8_t* sourceChar = (uint8_t*)source;
while (length > 0)
{
*destChar = *sourceChar;
sourceChar++;
destChar++;
length--;
}
}
void handleGetLatLong(AsyncWebServerRequest *request)
{
_dln("API :: get lat long");
AsyncWebParameter* addressParam = request->getParam("address");
if (addressParam == nullptr)
{
request->send(400);
return;
}
String address = addressParam->value();
if (!address.length())
{
request->send(400);
return;
}
AsyncClient* httpClient = new AsyncClient();
httpClient->onError([&](void* arg, AsyncClient* client, int error)
{
_dln("API :: get lat long: OnError");
request->send(500);
delete client;
});
httpClient->onConnect([&](void * arg, AsyncClient* client)
{
client->onError(nullptr, nullptr);
ResponseBuffer* responseData = nullptr;
client->onData([&](void* arg, AsyncClient* c, void* data, size_t len)
{
_dln("> OnData");
// Store all received chunks in a linked list
ResponseBuffer* next = new ResponseBuffer;
next->data = malloc(len);
memcpy_unaligned(next->data, data, len);
next->length = len;
next->next = nullptr;
if (responseData == nullptr)
responseData = next;
else
{
ResponseBuffer* prev = responseData;
while (prev->next != nullptr)
prev = prev->next;
prev->next = next;
}
_dln("< OnData");
});
client->onDisconnect([&](void* arg, AsyncClient* c)
{
_dln("> OnDisconnect");
if (responseData == nullptr)
{
request->send(500);
return;
}
// Send back the linked list using a chunked response
ResponseBuffer* sendChunk = responseData;
size_t sendOffset = 0;
AsyncWebServerResponse *response = request->beginChunkedResponse("application/json", [&](uint8_t *buffer, size_t maxLen, size_t index) -> size_t
{
_dln("> ChunkedResponse");
if (sendOffset >= sendChunk->length)
{
// End of the chunk, go to the next one
sendChunk = sendChunk->next;
sendOffset = 0;
}
if (sendChunk == nullptr)
{
// We sent the last one, clean up the linked list
ResponseBuffer* next;
while (responseData != nullptr)
{
next = responseData->next;
free(responseData->data);
delete responseData;
responseData = next;
}
return 0;
}
if (maxLen >= sendChunk->length - sendOffset)
{
// Send the remainder of the chunk
memcpy_unaligned(buffer, (uint8_t*)sendChunk->data + sendOffset, sendChunk->length - sendOffset);
sendOffset = sendChunk->length;
}
else
{
memcpy_unaligned(buffer, (uint8_t*)sendChunk->data + sendOffset, maxLen);
sendOffset += maxLen;
}
_dln("< ChunkedResponse");
});
request->send(response);
delete c;
_dln("< OnDisconnect");
});
#ifdef SecretsPresent
String url = "/maps/api/geocode/json?address=" + urlencode(address) + "&key=" + urlencode(GoogleAPIKey);
#else
String url = "/maps/api/geocode/json?address=" + urlencode(address);
#endif
client->write(String("GET " + url + " HTTP/1.0\r\nHost: maps.googleapis.com\n\r\n").c_str());
});
if (!httpClient->connect("maps.googleapis.com", 443, true))
{
_dln("API :: get lat long: failed to connect to Google API");
delete httpClient;
request->send(500);
}
}
void registerGeocodeRoutes(AsyncWebServer* server)
{
server->on("/api/geo/latlong", HTTP_GET, handleGetLatLong);
}