Skip to content

Commit

Permalink
Improve HttpHeaders using HashMap(enum, String) (#1463)
Browse files Browse the repository at this point in the history
* Improve HttpHeaders using HashMap(enum, String), introduce CStringArray class
* Lookups faster
  • Loading branch information
mikee47 authored and slaff committed Oct 8, 2018
1 parent 7a47fe4 commit 95aae45
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 43 deletions.
55 changes: 55 additions & 0 deletions Sming/SmingCore/Data/CStringArray.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* @author: 2018 - Mikee47 <mike@sillyhouse.net>
*
****/

#include "CStringArray.h"

bool CStringArray::add(const char* str, unsigned length)
{
if(length == 0)
length = strlen(str);
if(!reserve(len + length + 1))
return false;
if(len)
buffer[len++] = '\0'; // Separator between strings
memcpy(buffer + len, str, length + 1); // Copy final nul terminator
len += length;
++count_;
return true;
}

int CStringArray::indexOf(const char* str) const
{
if(str == nullptr)
return -1;

unsigned index = 0;
for(unsigned offset = 0; offset < len; ++index) {
const char* s = buffer + offset;
if(strcasecmp(str, s) == 0)
return index;
offset += strlen(s) + 1;
}

return -1;
}

const char* CStringArray::getValue(unsigned index) const
{
if(index < count_) {
for(unsigned offset = 0; offset < length(); --index) {
const char* s = buffer + offset;
if(index == 0)
return s;
offset += strlen(s) + 1;
}
}

return nullptr;
}
95 changes: 95 additions & 0 deletions Sming/SmingCore/Data/CStringArray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* @author: 2018 - Mikee47 <mike@sillyhouse.net>
*
****/

#ifndef _SMING_CORE_DATA_STRING_ARRAY_H_
#define _SMING_CORE_DATA_STRING_ARRAY_H_

#include "WString.h"

/** @brief Class to manage a double null-terminated list of strings, such as "one\0two\0three"
* @note
*
* Comparison with Vector<String>
*
* Advantages:
* More memory efficient. Uses only a single heap allocation
* Useful for simple lookups, e.g. mapping enumerated values to strings
*
* Disadvantages:
*
* Slower. Items must be iterated using multiple strlen() calls
* Ordering not supported
* Insertions / deletions not supported (yet)
*/
class CStringArray : protected String
{
public:
/** @brief append a new string to the end of the array
* @param str
* @retval bool false on memory allocation error
*/
bool add(const char* str, unsigned length = 0);

bool add(const String& str)
{
return add(str.c_str(), str.length());
}

/** @brief Find the given string and return its index
* @param str String to find
* @retval int index of given string, -1 if not found
* @note comparison is not case-sensitive
*/
int indexOf(const char* str) const;

int indexOf(const String& str) const
{
return indexOf(str.c_str());
}

bool contains(const char* str) const
{
return indexOf(str) >= 0;
}

bool contains(const String& str) const
{
return indexOf(str) >= 0;
}

/** @brief Get string at the given position
* @param index 0-based index of string to obtain
* @retval const char* nullptr if index is not valid
*/
const char* getValue(unsigned index) const;

const char* operator[](unsigned index) const
{
return getValue(index);
}

/** @brief Empty the array
*/
void clear()
{
invalidate();
count_ = 0;
}

unsigned count() const
{
return count_;
}

private:
unsigned count_ = 0;
};

#endif // _SMING_CORE_DATA_STRING_ARRAY_H_
39 changes: 26 additions & 13 deletions Sming/SmingCore/Network/Http/HttpHeaders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,44 @@ static FSTR_TABLE(FieldNameStrings) = {
#undef XX
};

// Field names are not case sensitive
static bool headerKeyCompare(const String& a, const String& b)
String HttpHeaders::toString(HttpHeaderFieldName name) const
{
return a.equalsIgnoreCase(b);
}
if(name == HTTP_HEADER_UNKNOWN)
return nullptr;

HttpHeaders::HttpHeaders() : HashMap<String, String>(headerKeyCompare)
{
if(name < HTTP_HEADER_CUSTOM)
return *FieldNameStrings[name - 1];

return customFieldNames_[name - HTTP_HEADER_CUSTOM];
}

String HttpHeaders::toString(HttpHeaderFieldName name)
String HttpHeaders::toString(const String& name, const String& value)
{
if(name == HTTP_HEADER_UNKNOWN || name >= HTTP_HEADER_MAX)
return nullptr;

return *FieldNameStrings[name - 1];
String s;
s.reserve(name.length() + 2 + value.length() + 2);
s.concat(name);
s.concat(": ");
s.concat(value);
s.concat("\r\n");
return s;
}

HttpHeaderFieldName HttpHeaders::fromString(const String& name)
HttpHeaderFieldName HttpHeaders::fromString(const String& name) const
{
// 0 is reserved for UNKNOWN
for(unsigned i = 1; i < HTTP_HEADER_MAX; ++i) {
for(unsigned i = 1; i < HTTP_HEADER_CUSTOM; ++i) {
if(name.equalsIgnoreCase(*FieldNameStrings[i - 1]))
return static_cast<HttpHeaderFieldName>(i);
}

return findCustomFieldName(name);
}

HttpHeaderFieldName HttpHeaders::findCustomFieldName(const String& name) const
{
auto index = customFieldNames_.indexOf(name);
if(index >= 0)
return static_cast<HttpHeaderFieldName>(HTTP_HEADER_CUSTOM + index);

return HTTP_HEADER_UNKNOWN;
}
73 changes: 54 additions & 19 deletions Sming/SmingCore/Network/Http/HttpHeaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#ifndef _SMING_CORE_NETWORK_HTTP_HEADERS_H_
#define _SMING_CORE_NETWORK_HTTP_HEADERS_H_

#include "Data/CStringArray.h"
#include "WString.h"
#include "WHashMap.h"

Expand Down Expand Up @@ -79,7 +80,7 @@ enum HttpHeaderFieldName {
#define XX(_tag, _str, _comment) HTTP_HEADER_##_tag,
HTTP_HEADER_FIELDNAME_MAP(XX)
#undef XX
HTTP_HEADER_MAX
HTTP_HEADER_CUSTOM // First custom header tag value
};

/** @brief Encapsulates a set of HTTP header information
Expand All @@ -89,24 +90,19 @@ enum HttpHeaderFieldName {
*
* @todo add name and/or value escaping
*/
class HttpHeaders : public HashMap<String, String>
class HttpHeaders : public HashMap<HttpHeaderFieldName, String>
{
public:
HttpHeaders();

static String toString(HttpHeaderFieldName name);
String toString(HttpHeaderFieldName name) const;

/** @brief Produce a string for output in the HTTP header, with line ending
* @param name
* @param value
* @retval String
*/
static String toString(const String& name, const String& value)
{
return name + ": " + value + "\r\n";
}
static String toString(const String& name, const String& value);

static String toString(HttpHeaderFieldName name, const String& value)
String toString(HttpHeaderFieldName name, const String& value) const
{
return toString(toString(name), value);
}
Expand All @@ -116,37 +112,61 @@ class HttpHeaders : public HashMap<String, String>
* @retval HttpHeaderFieldName field name code, HTTP_HEADER_UNKNOWN if not recognised
* @note comparison is not case-sensitive
*/
static HttpHeaderFieldName fromString(const String& name);
HttpHeaderFieldName fromString(const String& name) const;

using HashMap::operator[];

const String& operator[](HttpHeaderFieldName name) const
/** @brief Fetch a reference to the header field value by name
* @param name
* @retval const String& Reference to value
* @note if the field doesn't exist a null String reference is returned
*/
const String& operator[](const String& name) const
{
return operator[](toString(name));
auto field = fromString(name);
if(field == HTTP_HEADER_UNKNOWN) {
return nil;
}
return operator[](field);
}

String& operator[](HttpHeaderFieldName name)
/** @brief Fetch a reference to the header field value by name
* @param name
* @retval String& Reference to value
* @note if the field doesn't exist it is created with the default null value
*/
String& operator[](const String& name)
{
return operator[](toString(name));
auto field = fromString(name);
if(field == HTTP_HEADER_UNKNOWN) {
field = static_cast<HttpHeaderFieldName>(HTTP_HEADER_CUSTOM + customFieldNames_.count());
customFieldNames_.add(name);
}
return operator[](field);
}

/** @brief Return the HTTP header line for the value at the given index
* @param index
* @retval String
* @note if the index is invalid,
*/
String operator[](unsigned index) const
{
return toString(keyAt(index), valueAt(index));
}

using HashMap::contains;

bool contains(HttpHeaderFieldName name)
bool contains(const String& name)
{
return contains(toString(name));
return contains(fromString(name));
}

using HashMap::remove;

void remove(HttpHeaderFieldName name)
void remove(const String& name)
{
remove(toString(name));
remove(fromString(name));
}

HttpHeaders& operator=(const HttpHeaders& headers)
Expand All @@ -155,6 +175,21 @@ class HttpHeaders : public HashMap<String, String>
setMultiple(headers);
return *this;
}

void clear()
{
customFieldNames_.clear();
HashMap::clear();
}

private:
/** @brief Try to match a string against the list of custom field names
* @param name
* @retval HttpHeaderFieldName HTTP_HEADER_UNKNOWN if not found
*/
HttpHeaderFieldName findCustomFieldName(const String& name) const;

CStringArray customFieldNames_;
};

#endif /* _SMING_CORE_NETWORK_HTTP_HEADERS_H_ */
4 changes: 2 additions & 2 deletions Sming/SmingCore/Network/Http/HttpRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,13 @@ String HttpRequest::toString()
#endif

content += String(http_method_str(method)) + ' ' + uri.getPathWithQuery() + _F(" HTTP/1.1\n");
content += F("Host: ") + uri.Host + ':' + uri.Port + '\n';
content += headers.toString(HTTP_HEADER_HOST, uri.Host + ':' + uri.Port);
for(unsigned i = 0; i < headers.count(); i++) {
content += headers[i];
}

if(stream != nullptr && stream->available() >= 0) {
content += HttpHeaders::toString(HTTP_HEADER_CONTENT_LENGTH, String(stream->available()));
content += headers.toString(HTTP_HEADER_CONTENT_LENGTH, String(stream->available()));
}

return content;
Expand Down
16 changes: 9 additions & 7 deletions Sming/SmingCore/Network/WebsocketClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ bool WebsocketClient::connect(String url, uint32_t sslOptions /* = 0 */)
sendString("/");
}
sendString(F(" HTTP/1.1\r\n"));
sendString(HttpHeaders::toString(HTTP_HEADER_UPGRADE, WSSTR_WEBSOCKET));
sendString(HttpHeaders::toString(HTTP_HEADER_CONNECTION, WSSTR_UPGRADE));
sendString(HttpHeaders::toString(HTTP_HEADER_HOST, _uri.Host));
sendString(HttpHeaders::toString(HTTP_HEADER_SEC_WEBSOCKET_KEY, _key));
sendString(HttpHeaders::toString(HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL, F("chat")));
sendString(HttpHeaders::toString(HTTP_HEADER_SEC_WEBSOCKET_VERSION, String(WEBSOCKET_VERSION)));
HttpHeaders headers;
sendString(headers.toString(HTTP_HEADER_UPGRADE, WSSTR_WEBSOCKET));
sendString(headers.toString(HTTP_HEADER_CONNECTION, WSSTR_UPGRADE));
sendString(headers.toString(HTTP_HEADER_HOST, _uri.Host));
sendString(headers.toString(HTTP_HEADER_SEC_WEBSOCKET_KEY, _key));
sendString(headers.toString(HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL, F("chat")));
sendString(headers.toString(HTTP_HEADER_SEC_WEBSOCKET_VERSION, String(WEBSOCKET_VERSION)));
sendString("\r\n", false);
return true;
}
Expand All @@ -82,7 +83,8 @@ void WebsocketClient::onError(err_t err)

bool WebsocketClient::_verifyKey(char* buf, int size)
{
String wsa = HttpHeaders::toString(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, String::nullstr);
HttpHeaders headers;
String wsa = headers.toString(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, String::nullstr);
const char* serverHashedKey = strstri(buf, wsa.c_str());

if(!serverHashedKey) {
Expand Down
Loading

0 comments on commit 95aae45

Please sign in to comment.