/* * Stairs * Copyright 2017 (c) Mark van Renswoude * * https://git.x2software.net/pub/Stairs */ #include "geocode.h" #include #include #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); }