Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/marian-nmt/Simple-WebSocket-Server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreidheim <eidheim@gmail.com>2017-07-19 09:30:58 +0300
committereidheim <eidheim@gmail.com>2017-07-19 09:30:58 +0300
commit6deb487a1ab266b9d7347f4e1d9fafc07e267c23 (patch)
treee6518fcf5e3260bbf3632eadcf16b4d0941f23f4 /utility.hpp
parent127f177a34d8f19ed32218be0f4fbe9693ca8760 (diff)
Moved request and response message parsing to utility. Also now uses the same utility/status_code.hpp as in Simple-Web-Server
Diffstat (limited to 'utility.hpp')
-rw-r--r--utility.hpp337
1 files changed, 330 insertions, 7 deletions
diff --git a/utility.hpp b/utility.hpp
index 02460ae..70f81db 100644
--- a/utility.hpp
+++ b/utility.hpp
@@ -1,14 +1,13 @@
-#ifndef SIMPLE_WEBSOCKET_SERVER_UTILITY_HPP
-#define SIMPLE_WEBSOCKET_SERVER_UTILITY_HPP
+#ifndef SIMPLE_WEB_UTILITY_HPP
+#define SIMPLE_WEB_UTILITY_HPP
+#include "status_code.hpp"
#include <iostream>
+#include <memory>
#include <string>
#include <unordered_map>
namespace SimpleWeb {
-#ifndef CASE_INSENSITIVE_EQUAL_AND_HASH
-#define CASE_INSENSITIVE_EQUAL_AND_HASH
-
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) {
return str1.size() == str2.size() &&
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
@@ -32,9 +31,333 @@ namespace SimpleWeb {
return h;
}
};
-#endif
typedef std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual> CaseInsensitiveMultimap;
+
+ /// Percent encoding and decoding
+ class Percent {
+ public:
+ /// Returns percent-encoded string
+ static std::string encode(const std::string &value) {
+ static auto hex_chars = "0123456789ABCDEF";
+
+ std::string result;
+ result.reserve(value.size()); // Minimum size of result
+
+ for(auto &chr : value) {
+ if(chr == ' ')
+ result += '+';
+ else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
+ result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
+ else
+ result += chr;
+ }
+
+ return result;
+ }
+
+ /// Returns percent-decoded string
+ static std::string decode(const std::string &value) {
+ std::string result;
+ result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
+
+ for(size_t i = 0; i < value.size(); ++i) {
+ auto &chr = value[i];
+ if(chr == '%' && i + 2 < value.size()) {
+ auto hex = value.substr(i + 1, 2);
+ auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
+ result += decoded_chr;
+ i += 2;
+ }
+ else if(chr == '+')
+ result += ' ';
+ else
+ result += chr;
+ }
+
+ return result;
+ }
+ };
+
+ /// Query string creation and parsing
+ class QueryString {
+ public:
+ /// Returns query string created from given field names and values
+ static std::string create(const CaseInsensitiveMultimap &fields) {
+ std::string result;
+
+ bool first = true;
+ for(auto &field : fields) {
+ result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
+ first = false;
+ }
+
+ return result;
+ }
+
+ /// Returns query keys with percent-decoded values.
+ static CaseInsensitiveMultimap parse(const std::string &query_string) {
+ CaseInsensitiveMultimap result;
+
+ if(query_string.empty())
+ return result;
+
+ size_t name_pos = 0;
+ size_t name_end_pos = -1;
+ size_t value_pos = -1;
+ for(size_t c = 0; c < query_string.size(); ++c) {
+ if(query_string[c] == '&') {
+ auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
+ if(!name.empty()) {
+ auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
+ result.emplace(std::move(name), Percent::decode(value));
+ }
+ name_pos = c + 1;
+ name_end_pos = -1;
+ value_pos = -1;
+ }
+ else if(query_string[c] == '=') {
+ name_end_pos = c;
+ value_pos = c + 1;
+ }
+ }
+ if(name_pos < query_string.size()) {
+ auto name = query_string.substr(name_pos, name_end_pos - name_pos);
+ if(!name.empty()) {
+ auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
+ result.emplace(std::move(name), Percent::decode(value));
+ }
+ }
+
+ return result;
+ }
+ };
+
+
+ class RequestMessage {
+ public:
+ /// Parse request line and header fields
+ static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) {
+ header.clear();
+ std::string line;
+ getline(stream, line);
+ size_t method_end;
+ if((method_end = line.find(' ')) != std::string::npos) {
+ method = line.substr(0, method_end);
+
+ size_t query_start = std::string::npos;
+ size_t path_and_query_string_end = std::string::npos;
+ for(size_t i = method_end + 1; i < line.size(); ++i) {
+ if(line[i] == '?' && (i + 1) < line.size())
+ query_start = i + 1;
+ else if(line[i] == ' ') {
+ path_and_query_string_end = i;
+ break;
+ }
+ }
+ if(path_and_query_string_end != std::string::npos) {
+ if(query_start != std::string::npos) {
+ path = line.substr(method_end + 1, query_start - method_end - 2);
+ query_string = line.substr(query_start, path_and_query_string_end - query_start);
+ }
+ else
+ path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
+
+ size_t protocol_end;
+ if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
+ if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
+ return false;
+ version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
+ }
+ else
+ return false;
+
+ getline(stream, line);
+ size_t param_end;
+ while((param_end = line.find(':')) != std::string::npos) {
+ size_t value_start = param_end + 1;
+ if(value_start < line.size()) {
+ if(line[value_start] == ' ')
+ value_start++;
+ if(value_start < line.size())
+ header.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
+ }
+
+ getline(stream, line);
+ }
+ }
+ else
+ return false;
+ }
+ else
+ return false;
+ return true;
+ }
+ };
+
+ class ResponseMessage {
+ public:
+ /// Parse status line and header fields
+ static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) {
+ header.clear();
+ std::string line;
+ getline(stream, line);
+ size_t version_end = line.find(' ');
+ if(version_end != std::string::npos) {
+ if(5 < line.size())
+ version = line.substr(5, version_end - 5);
+ else
+ return false;
+ if((version_end + 1) < line.size())
+ status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);
+ else
+ return false;
+
+ getline(stream, line);
+ size_t param_end;
+ while((param_end = line.find(':')) != std::string::npos) {
+ size_t value_start = param_end + 1;
+ if((value_start) < line.size()) {
+ if(line[value_start] == ' ')
+ value_start++;
+ if(value_start < line.size())
+ header.insert(std::make_pair(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1)));
+ }
+
+ getline(stream, line);
+ }
+ }
+ else
+ return false;
+ return true;
+ }
+ };
+} // namespace SimpleWeb
+
+#ifdef PTHREAD_RWLOCK_INITIALIZER
+namespace SimpleWeb {
+ /// Read-preferring R/W lock.
+ /// Uses pthread_rwlock.
+ class SharedMutex {
+ pthread_rwlock_t rwlock;
+
+ public:
+ class SharedLock {
+ friend class SharedMutex;
+ pthread_rwlock_t &rwlock;
+
+ SharedLock(pthread_rwlock_t &rwlock) : rwlock(rwlock) {
+ pthread_rwlock_rdlock(&rwlock);
+ }
+
+ public:
+ ~SharedLock() {
+ pthread_rwlock_unlock(&rwlock);
+ }
+ };
+
+ class UniqueLock {
+ friend class SharedMutex;
+ pthread_rwlock_t &rwlock;
+
+ UniqueLock(pthread_rwlock_t &rwlock) : rwlock(rwlock) {
+ pthread_rwlock_wrlock(&rwlock);
+ }
+
+ public:
+ ~UniqueLock() {
+ pthread_rwlock_unlock(&rwlock);
+ }
+ };
+
+ public:
+ SharedMutex() {
+
+ pthread_rwlock_init(&rwlock, nullptr);
+ }
+
+ ~SharedMutex() {
+ pthread_rwlock_destroy(&rwlock);
+ }
+
+ std::unique_ptr<SharedLock> shared_lock() {
+ return std::unique_ptr<SharedLock>(new SharedLock(rwlock));
+ }
+
+ std::unique_ptr<UniqueLock> unique_lock() {
+ return std::unique_ptr<UniqueLock>(new UniqueLock(rwlock));
+ }
+ };
} // namespace SimpleWeb
+#else
+#include <condition_variable>
+#include <mutex>
+namespace SimpleWeb {
+ /// Read-preferring R/W lock.
+ /// Based on https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Using_a_condition_variable_and_a_mutex pseudocode.
+ /// TODO: Someone that uses Windows should implement Windows specific R/W locks here.
+ class SharedMutex {
+ std::mutex m;
+ std::condition_variable c;
+ int r = 0;
+ bool w = false;
+
+ public:
+ class SharedLock {
+ friend class SharedMutex;
+ std::condition_variable &c;
+ int &r;
+ std::unique_lock<std::mutex> lock;
+
+ SharedLock(std::mutex &m, std::condition_variable &c, int &r, bool &w) : c(c), r(r), lock(m) {
+ while(w)
+ c.wait(lock);
+ ++r;
+ lock.unlock();
+ }
+
+ public:
+ ~SharedLock() {
+ lock.lock();
+ --r;
+ if(r == 0)
+ c.notify_all();
+ lock.unlock();
+ }
+ };
+
+ class UniqueLock {
+ friend class SharedMutex;
+ std::condition_variable &c;
+ bool &w;
+ std::unique_lock<std::mutex> lock;
+
+ UniqueLock(std::mutex &m, std::condition_variable &c, int &r, bool &w) : c(c), w(w), lock(m) {
+ while(w || r > 0)
+ c.wait(lock);
+ w = true;
+ lock.unlock();
+ }
+
+ public:
+ ~UniqueLock() {
+ lock.lock();
+ w = false;
+ c.notify_all();
+ lock.unlock();
+ }
+ };
+
+ public:
+ std::unique_ptr<SharedLock> shared_lock() {
+ return std::unique_ptr<SharedLock>(new SharedLock(m, c, r, w));
+ }
+
+ std::unique_ptr<UniqueLock> unique_lock() {
+ return std::unique_ptr<UniqueLock>(new UniqueLock(m, c, r, w));
+ }
+ };
+} // namespace SimpleWeb
+#endif
-#endif // SIMPLE_WEBSOCKET_SERVER_UTILITY_HPP
+#endif // SIMPLE_WEB_UTILITY_HPP