Stairs/src/server/geocode.cpp

240 lines
5.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"
#ifdef SecretsPresent
#include "../secret.h"
#endif
struct ResponseBuffer
{
void* data;
size_t length;
ResponseBuffer* next;
};
struct RequestArg
{
String address;
AsyncWebServerRequest *request;
ResponseBuffer* responseData;
size_t responseOffset;
};
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 mapsGeocodeDisconnect(void* arg, AsyncClient* c);
void mapsGeocodeData(void* arg, AsyncClient* c, void* data, size_t len);
void mapsGeocodeConnect(void* arg, AsyncClient* client)
{
RequestArg* requestArg = (RequestArg*)arg;
client->onError(nullptr, nullptr);
client->onData(mapsGeocodeData, arg);
client->onDisconnect(mapsGeocodeDisconnect, arg);
#ifdef SecretsPresent
String url = "/maps/api/geocode/json?address=" + urlencode(requestArg->address) + "&key=" + urlencode(GoogleAPIKey);
#else
String url = "/maps/api/geocode/json?address=" + urlencode(requestArg->address);
#endif
client->write(String("GET " + url + " HTTP/1.0\r\nHost: maps.googleapis.com\n\r\n").c_str());
}
void mapsGeocodeData(void* arg, AsyncClient* c, void* data, size_t len)
{
RequestArg* requestArg = (RequestArg*)arg;
_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 (requestArg->responseData == nullptr)
requestArg->responseData = next;
else
{
ResponseBuffer* prev = requestArg->responseData;
while (prev->next != nullptr)
prev = prev->next;
prev->next = next;
}
_dln("< OnData");
}
void mapsGeocodeDisconnect(void* arg, AsyncClient* client)
{
RequestArg* requestArg = (RequestArg*)arg;
_dln("> OnDisconnect");
if (requestArg->responseData == nullptr)
{
requestArg->request->send(500);
return;
}
// Send back the linked list using a chunked response
AsyncWebServerResponse *response = requestArg->request->beginChunkedResponse("application/json", [requestArg](uint8_t *buffer, size_t maxLen, size_t index) -> size_t
{
_dln("> ChunkedResponse");
if (requestArg->responseOffset >= requestArg->responseData->length)
{
// End of the chunk, go to the next one
ResponseBuffer* next = requestArg->responseData->next;
delete requestArg->responseData;
requestArg->responseData = next;
requestArg->responseOffset = 0;
}
if (requestArg->responseData == nullptr)
{
// We sent the last one, clean up what remains
ResponseBuffer* next;
while (requestArg->responseData != nullptr)
{
next = requestArg->responseData->next;
free(requestArg->responseData->data);
delete requestArg->responseData;
requestArg->responseData = next;
}
return 0;
}
if (maxLen >= requestArg->responseData->length - requestArg->responseOffset)
{
// Send the remainder of the chunk
memcpy_unaligned(buffer, (uint8_t*)requestArg->responseData->data + requestArg->responseOffset, requestArg->responseData->length - requestArg->responseOffset);
requestArg->responseOffset = requestArg->responseData->length;
}
else
{
memcpy_unaligned(buffer, (uint8_t*)requestArg->responseData->data + requestArg->responseOffset, maxLen);
requestArg->responseOffset += maxLen;
}
_dln("< ChunkedResponse");
});
requestArg->request->send(response);
delete requestArg;
delete client;
_dln("< OnDisconnect");
}
void handleGetLatLong(AsyncWebServerRequest *request)
{
_dln("API :: get lat long");
RequestArg* requestArg = new RequestArg();
requestArg->request = request;
requestArg->responseData = nullptr;
requestArg->responseOffset = 0;
AsyncWebParameter* addressParam = request->getParam("address");
if (addressParam == nullptr)
{
request->send(400);
delete requestArg;
return;
}
requestArg->address = addressParam->value();
if (!requestArg->address.length())
{
request->send(400);
delete requestArg;
return;
}
AsyncClient* httpClient = new AsyncClient();
httpClient->onError([](void* arg, AsyncClient* client, int error)
{
RequestArg* requestArg = (RequestArg*)arg;
_dln("API :: get lat long: OnError");
requestArg->request->send(500);
delete requestArg;
delete client;
});
httpClient->onConnect(mapsGeocodeConnect, requestArg);
if (!httpClient->connect("maps.googleapis.com", 443, true))
{
_dln("API :: get lat long: failed to connect to Google API");
delete requestArg;
delete httpClient;
request->send(500);
}
}
void registerGeocodeRoutes(AsyncWebServer* server)
{
server->on("/api/geo/latlong", HTTP_GET, handleGetLatLong);
}