Bläddra i källkod

Z991239-360 #comment fea 提交子模块requests用于检测网络连通性

陈良瑜80374463 5 år sedan
förälder
incheckning
6bdb2d0948

+ 66 - 0
ThirdParty/Include/requests/include/requests/AsyncRequest.hpp

@@ -0,0 +1,66 @@
+#ifndef ASYNCREQUEST_H
+#define ASYNCREQUEST_H
+
+#include "Callback.hpp"
+
+#include <boost/asio.hpp>
+#include <memory>
+#include <unordered_map>
+#include <thread>
+#include <vector>
+
+namespace requests {
+
+class Url;
+class Context;
+    
+class AsyncRequest
+{
+public:
+    using IOService  = boost::asio::io_service;
+    using Resolver   = boost::asio::ip::tcp::resolver;
+    using Socket     = boost::asio::ip::tcp::socket;
+    using ErrorCode  = boost::system::error_code;
+    using Buffer     = boost::asio::streambuf;
+    using String     = std::string;   
+    using Work       = IOService::work;
+    using StringMap  = std::unordered_map<String, String>;
+    using ContextPtr = std::shared_ptr<Context>;
+    
+    AsyncRequest(std::size_t threadNum = std::thread::hardware_concurrency());
+    ~AsyncRequest();
+
+    // disable the copy operations
+    AsyncRequest(const AsyncRequest &) = delete;
+    AsyncRequest &operator=(const AsyncRequest &) = delete;
+
+    // enable the move operations
+    AsyncRequest(AsyncRequest &&) = default;
+    AsyncRequest &operator=(AsyncRequest &&) = default;
+
+    void get(const Url &url, UserCallback cb, ErrorCallback errorCb);
+    
+    void get(const Url &url, const StringMap &params, UserCallback cb, ErrorCallback errorCb);
+
+    void post(const Url &url, const StringMap &data, UserCallback cb, ErrorCallback errorCb);
+    
+private:    
+    void handleResolve(const ErrorCode &err, Resolver::iterator iter, ContextPtr context);
+
+    void handleConnect(const ErrorCode &err, Resolver::iterator iter, ContextPtr context);
+    
+    void handleWrite(const ErrorCode &err, ContextPtr context);
+
+    void handleReadHeaders(const ErrorCode &err, ContextPtr context);
+
+    void handleReadBody(const ErrorCode &err, ContextPtr context);
+
+    IOService                       service_;
+    Resolver                        resolver_;
+    std::unique_ptr<Work>           work_;
+    std::vector<std::thread>        threads_;
+};
+
+} // namespace requests
+
+#endif /* ASYNCREQUEST_H */

+ 17 - 0
ThirdParty/Include/requests/include/requests/Callback.hpp

@@ -0,0 +1,17 @@
+#ifndef CALLBACK_H
+#define CALLBACK_H
+
+#include <functional>
+
+namespace requests {
+    
+class Response;
+class Exception;
+    
+using UserCallback  = std::function<void (Response &)>;
+using ErrorCallback = std::function<void (const Exception &)>;
+    
+};
+
+
+#endif /* CALLBACK_H */

+ 129 - 0
ThirdParty/Include/requests/include/requests/Context.hpp

@@ -0,0 +1,129 @@
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+#include "Callback.hpp"
+#include "Response.hpp"
+#include "Url.hpp"
+
+#include <assert.h>
+#include <boost/asio.hpp>
+#include <unordered_map>
+
+namespace requests {
+
+class Context
+{
+public:
+    using IOService  = boost::asio::io_service;
+    using Socket     = boost::asio::ip::tcp::socket;
+    using Buffer     = boost::asio::streambuf;
+    using String     = std::string;
+    using StringMap  = std::unordered_map<String, String>;
+
+    enum class Method { Get, Post };
+
+    Context(IOService &service, const Url &url, Method method, const StringMap &data);
+    
+    Context(IOService &service, const Url &url, Method method, const StringMap &data, UserCallback cb, ErrorCallback errorCb);
+
+    // disable the copy operations
+    Context(const Context &) = delete;
+    Context &operator=(const Context &) = delete;
+
+    // enable the move operations
+    Context(Context &&) = default;
+    Context &operator=(Context &&) = default;
+    
+    const Socket &socket() const
+    {
+        return sock_;
+    }    
+
+    const Url &url() const
+    {
+        return url_;
+    }
+    
+    const Buffer &reqBuff() const
+    {
+        return requestBuff_;
+    }
+    
+    const Buffer &respBuff() const
+    {
+        return responseBuff_;
+    }
+
+    Socket &socket()
+    {
+        return sock_;
+    }    
+
+    Url &url()
+    {
+        return url_;
+    }
+    
+    Buffer &reqBuff()
+    {
+        return requestBuff_;
+    }
+    
+    Buffer &respBuff()
+    {
+        return responseBuff_;
+    }
+    
+    void setHeaders(const String &headers)
+    {
+        headers_ = headers;
+    }
+    
+    void setHeaders(String &&headers) noexcept
+    {
+        headers_ = std::move(headers);
+    }
+    
+    void appendContent(const String &content)
+    {
+        content_.append(content);
+    }
+    
+    void handleResponse()
+    {
+        assert(callback_);
+        
+        Response resp(std::move(headers_), std::move(content_));
+        callback_(resp);
+    }    
+
+    void handleError(Exception &e)
+    {
+        assert(errorCallback_);
+
+        errorCallback_(e);        
+    }
+    
+    Response getResponse()
+    {
+        assert(!callback_);
+        
+        Response resp(std::move(headers_), std::move(content_));
+        return resp;
+    }
+    
+private:
+    Socket        sock_;
+    Url           url_;
+    UserCallback  callback_;
+    ErrorCallback errorCallback_;    
+    Method        method_;
+    Buffer        requestBuff_;
+    Buffer        responseBuff_;
+    String        headers_;
+    String        content_;
+};
+
+}  // namespace requests
+
+#endif /* CONTEXT_H */

+ 24 - 0
ThirdParty/Include/requests/include/requests/Exception.hpp

@@ -0,0 +1,24 @@
+#ifndef EXCEPTION_H
+#define EXCEPTION_H
+
+#include <exception>
+#include <string>
+
+namespace requests {
+    
+class Exception : public std::exception
+{
+public:
+    explicit Exception(const std::string &message);
+
+    explicit Exception(std::string &&message);
+    
+    virtual const char *what() const noexcept override;
+    
+private:
+    std::string message_;
+};
+
+} // namespace requests
+
+#endif /* EXCEPTION_H */

+ 64 - 0
ThirdParty/Include/requests/include/requests/Request.hpp

@@ -0,0 +1,64 @@
+#ifndef REQUESTS_H
+#define REQUESTS_H
+
+#include "Callback.hpp"
+#include "Response.hpp"
+
+#include <boost/asio.hpp>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace requests {    
+
+class Url;
+class Context;
+   
+class Request
+{
+public:
+    using IOService   = boost::asio::io_service;
+    using Resolver    = boost::asio::ip::tcp::resolver;
+    using Socket      = boost::asio::ip::tcp::socket;
+    using SocketPtr   = std::unique_ptr<Socket>;
+    using ErrorCode   = boost::system::error_code;
+    using Buffer      = boost::asio::streambuf;
+    using String      = std::string;
+    using StringMap   = std::unordered_map<String, String>;
+    
+    Request();
+
+    // disable the copy operations
+    Request(const Request &) = delete;
+    Request &operator=(const Request &) = delete;
+
+    // enable the move operations
+    Request(Request &&) = default;
+    Request &operator=(Request &&) = default;
+
+    Response get(const Url &url);
+    
+    Response get(const Url &url, const StringMap &params);
+    
+    Response post(const Url &url, const StringMap &data);            
+private:
+    // make HTTP request and read response
+    void makeHttpRequest(Context &context);
+
+    // resolve domain name synchronously
+    void syncResolve(Context &context);
+
+    // send HTTP request headers and body synchronously
+    void syncSendRequest(Context &context);
+
+    // read HTTP response synchronously
+    void syncReadResponse(Context &context);
+        
+    IOService service_;
+    Resolver  resolver_;
+};
+
+} // namespace requests
+
+#endif /* REQUESTS_H */

+ 59 - 0
ThirdParty/Include/requests/include/requests/Response.hpp

@@ -0,0 +1,59 @@
+#ifndef RESPONSE_H
+#define RESPONSE_H
+
+#include <string>
+#include <vector>
+#include <unordered_map>
+
+namespace requests {
+
+class Response
+{
+public:
+    using String     = std::string;
+    using StringList = std::vector<String>;
+    using StringMap  = std::unordered_map<String, String>;
+    
+    Response(const String &headers, const String &content);
+
+    unsigned short statusCode() const
+    {
+        return statusCode_;
+    }
+
+    const String &content() const
+    {
+        return content_;
+    }
+
+    const String &statusMessage() const
+    {
+        return statusMessage_;
+    }
+
+    const StringMap &headers() const
+    {
+        return headers_;
+    }   
+
+    StringMap &headers()
+    {
+        return headers_;
+    }
+    
+private:
+    void splitStatusLine(const String &statusLine);
+
+    void splitHeader(const String &header);
+    
+    String                               headersStr_;
+    String                               content_;
+    String                               version_;
+    String                               statusMessage_;
+    unsigned short                       statusCode_;        
+    StringMap                            headers_;
+};
+
+} // namespace requests
+
+#endif /* RESPONSE_H */

+ 48 - 0
ThirdParty/Include/requests/include/requests/Url.hpp

@@ -0,0 +1,48 @@
+#ifndef URL_H
+#define URL_H
+
+#include <string>
+#include <unordered_map>
+
+namespace requests {
+
+class Url
+{
+public:
+    using String     = std::string;
+    using StringMap  = std::unordered_map<String, String>;
+    
+    explicit Url(const String &url);
+
+    void addQueries(const StringMap &params);
+    
+    bool hasQueries() const;
+
+    String pathAndQueries() const;
+
+    const String &host() const;
+
+    const String &port() const;
+    
+    const String &schema() const;
+
+    const String &path() const;
+
+    const String &queries() const;
+
+    String toString() const;
+    
+private:
+    String         url_;
+    String         schema_;
+    String         host_;
+    String         port_;
+    String         path_;
+    String         queries_;    
+};
+
+std::ostream &operator<<(std::ostream &os, const Url &url);
+    
+} // namespace requests
+
+#endif /* URL_H */

+ 23 - 0
ThirdParty/Include/requests/include/requests/Utils.hpp

@@ -0,0 +1,23 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <boost/asio.hpp>
+#include <vector>
+#include <string>
+#include <unordered_map>
+
+namespace requests {
+
+std::vector<std::string> splitString(const std::string &str, const std::string &separator);
+    
+std::vector<std::string> splitString(const std::string &str, const std::string &separator, std::size_t splitTimes);
+
+std::string bufferToString(boost::asio::streambuf &buffer);
+
+std::string join(const std::string &separator, const std::vector<std::string> &strs);
+    
+std::string urlEncode(const std::unordered_map<std::string, std::string> &params);
+    
+} // namespace requests
+
+#endif /* UTILS_H */

+ 206 - 0
ThirdParty/Include/requests/requests/AsyncRequest.cpp

@@ -0,0 +1,206 @@
+#include <requests/AsyncRequest.hpp>
+#include <requests/Context.hpp>
+#include <requests/Exception.hpp>
+#include <requests/Url.hpp>
+#include <requests/Utils.hpp>
+#include <iostream>
+
+using namespace requests;
+
+AsyncRequest::AsyncRequest(std::size_t threadNum)
+    : service_(),
+      resolver_(service_),
+      work_(new Work(service_))
+{
+    for (int i = 0; i < threadNum; ++i)
+    {
+        threads_.emplace_back([this] ()
+                              {
+                                  service_.run();
+                              });
+    }
+}
+
+AsyncRequest::~AsyncRequest()
+{
+    work_.reset(nullptr);
+
+    for (auto &t: threads_)
+    {
+        t.join();
+    }
+}
+
+void AsyncRequest::get(const Url &url, UserCallback cb, ErrorCallback errorCb)
+{
+    // empty query parameters
+    StringMap params;
+        
+    return get(url, params, cb, errorCb);
+}
+    
+void AsyncRequest::get(const Url &url, const StringMap &params, UserCallback cb, ErrorCallback errorCb)
+{
+    Resolver::query query(url.host(), url.port());
+        
+    auto context = std::make_shared<Context>(service_, url, Context::Method::Get, params, cb, errorCb);        
+    
+    resolver_.async_resolve(query,
+                            [this, context] (const ErrorCode &err, Resolver::iterator iter)
+                            {
+                                handleResolve(err, iter, context);
+                            });                
+}    
+
+void AsyncRequest::post(const Url &url, const StringMap &data, UserCallback cb, ErrorCallback errorCb)
+{ 
+    Resolver::query query(url.host(), url.port());
+
+    auto context = std::make_shared<Context>(service_, url, Context::Method::Post, data, cb, errorCb);        
+        
+    resolver_.async_resolve(query,
+                            [this, context] (const ErrorCode &err, Resolver::iterator iter)
+                            {
+                                handleResolve(err, iter, context);
+                            });                        
+}
+    
+void AsyncRequest::handleResolve(const ErrorCode &err, Resolver::iterator iter, ContextPtr context)
+{
+    if (err)
+    {        
+        Exception e("Resolve domain name error: " + err.message());
+        context->handleError(e);
+        return;        
+    }
+        
+    auto endpoint = iter->endpoint();
+    auto &sock = context->socket();
+
+    ++iter;        
+    sock.async_connect(endpoint,
+                       [this, iter, context] (const ErrorCode &err)
+                       {
+                           handleConnect(err, iter, context);
+                       });
+}
+
+void AsyncRequest::handleConnect(const ErrorCode &err, Resolver::iterator iter, ContextPtr context)
+{
+    auto &sock = context->socket();
+    auto &buffer = context->reqBuff();
+        
+    if (err)
+    {
+        Resolver::iterator nullIter;            
+        if (iter == nullIter)
+        {            
+            Exception e("Resolve domain name error: " + err.message());
+            context->handleError(e);
+            return;            
+        }
+        sock.close();
+            
+        ErrorCode nullErr;
+        handleResolve(nullErr, iter, context);            
+    }
+    else
+    {
+        boost::asio::async_write(sock, buffer,
+                                 [this, context] (const ErrorCode &err,
+                                                  std::size_t /* bytesTransferred */) 
+                                 {
+                                     handleWrite(err, context);
+                                 });            
+    }
+}
+    
+void AsyncRequest::handleWrite(const ErrorCode &err, ContextPtr context)
+{
+    if (err)
+    {
+        Exception e("Send request headers to server error: " + err.message());
+        context->handleError(e);
+        return;
+    }
+
+    auto &sock = context->socket();
+    auto &respBuffer = context->respBuff();
+
+    // shutdown on write
+    sock.shutdown(Socket::shutdown_send);
+        
+    boost::asio::async_read_until(sock, respBuffer, "\r\n\r\n",
+                                  [this, context] (const ErrorCode &err,
+                                                   std::size_t /* bytesTransferred */)
+                                  {
+                                      handleReadHeaders(err, context);
+                                  });
+}
+
+void AsyncRequest::handleReadHeaders(const ErrorCode &err, ContextPtr context)
+{
+    if (err)
+    {
+        Exception e("Read response headers error: " + err.message());
+        context->handleError(e);
+        return;        
+    }
+
+    auto &sock = context->socket();
+    auto &respBuffer = context->respBuff();
+
+    // NOTE: str may contain mutiple "\r\n\r\n"
+    auto str = bufferToString(respBuffer);
+    auto parts = splitString(str, "\r\n\r\n", 1);
+        
+    String headers, content;
+    headers = std::move(parts[0]);        
+    if (parts.size() == 2)
+    {
+        content = std::move(parts[1]);
+    }
+        
+    context->setHeaders(std::move(headers));
+    context->appendContent(content);
+                
+    boost::asio::async_read(sock,
+                            respBuffer,
+                            boost::asio::transfer_at_least(1),
+                            [this, context] (const ErrorCode &err,
+                                             std::size_t /* bytes_transferred */)
+                            {
+                                handleReadBody(err, context);
+                            });        
+}
+
+void AsyncRequest::handleReadBody(const ErrorCode &err, ContextPtr context)
+{
+    if (err)
+    {
+        if (err != boost::asio::error::eof)
+        {            
+            Exception e("Read response body error: " + err.message());
+            context->handleError(e);
+            return;            
+        }
+            
+        context->handleResponse();
+
+        return;
+    }
+
+    auto &sock = context->socket();
+    auto &respBuffer = context->respBuff();
+
+    auto content = bufferToString(respBuffer);
+    context->appendContent(content);
+        
+    boost::asio::async_read(sock, respBuffer,
+                            boost::asio::transfer_at_least(1),
+                            [this, context] (const ErrorCode &err,
+                                             std::size_t /* bytes_transferred */)
+                            {
+                                handleReadBody(err, context);
+                            });
+}

+ 12 - 0
ThirdParty/Include/requests/requests/CMakeLists.txt

@@ -0,0 +1,12 @@
+INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
+
+set(SRCS
+    Utils.cpp
+    Exception.cpp
+    Url.cpp
+    Response.cpp
+    Context.cpp
+    Request.cpp
+    AsyncRequest.cpp)
+
+add_library (requests STATIC ${SRCS})

+ 44 - 0
ThirdParty/Include/requests/requests/Context.cpp

@@ -0,0 +1,44 @@
+#include <requests/Context.hpp>
+#include <requests/Utils.hpp>
+#include <requests/Exception.hpp>
+
+using namespace requests;
+
+Context::Context(IOService &service, const Url &url, Method method, const StringMap &data)
+    : Context(service, url, method, data, UserCallback(), ErrorCallback())
+{        
+}        
+    
+Context::Context(IOService &service, const Url &url, Method method, const StringMap &data, UserCallback cb, ErrorCallback errorCb)
+    : sock_(service),
+      url_(url),
+      callback_(cb),
+      errorCallback_(errorCb),
+      method_(method)
+{
+    std::ostream reqStream(&requestBuff_);
+    
+    if (method_ == Method::Get)           
+    {
+        url_.addQueries(data);
+        
+        reqStream << "GET " << url_.pathAndQueries() << " HTTP/1.1\r\n";
+        reqStream << "Host: " << url_.host() << "\r\n";
+        reqStream << "Accept: */*\r\n";
+        reqStream << "Connection: close\r\n\r\n";                        
+    }
+    else if (method_ == Method::Post)
+    {
+        auto requestBody = urlEncode(data);
+        auto length = std::to_string(requestBody.size());
+        
+        reqStream << "POST " << url_.path() << " HTTP/1.1\r\n";
+        reqStream << "Host: " << url_.host() << "\r\n";
+        reqStream << "Accept: */*\r\n";
+        reqStream << "Content-Type: application/x-www-form-urlencoded\r\n";
+        reqStream << "Content-Length: " << length << "\r\n";
+        reqStream << "Connection: close\r\n\r\n";
+        
+        reqStream << requestBody;
+    }
+}

+ 19 - 0
ThirdParty/Include/requests/requests/Exception.cpp

@@ -0,0 +1,19 @@
+#include <requests/Exception.hpp>
+
+using namespace requests;
+
+Exception::Exception(const std::string &message) 
+    : message_(message)
+{
+}
+ 
+Exception::Exception(std::string &&message)
+    : message_(std::move(message))
+{        
+}
+    
+const char *Exception::what() const noexcept 
+{
+    return message_.c_str();
+}
+

+ 134 - 0
ThirdParty/Include/requests/requests/Request.cpp

@@ -0,0 +1,134 @@
+#include <requests/Context.hpp>
+#include <requests/Exception.hpp>
+#include <requests/Request.hpp>
+#include <requests/Url.hpp>
+#include <requests/Utils.hpp>
+
+using namespace requests;
+
+Request::Request()
+    : service_(),
+      resolver_(service_)
+{        
+}
+
+Response Request::get(const Url &url)
+{
+    StringMap emptyParams;
+
+    return get(url, emptyParams);
+}
+
+Response Request::get(const Url &url, const StringMap &params)
+{
+    Context context(service_, url, Context::Method::Get, params);
+    makeHttpRequest(context);        
+        
+    return context.getResponse();
+}
+
+Response Request::post(const Url &url, const StringMap &data)
+{
+    Context context(service_, url, Context::Method::Post, data);
+    makeHttpRequest(context);        
+        
+    return context.getResponse();
+}
+
+// make HTTP request and read response
+void Request::makeHttpRequest(Context &context)
+{
+    syncResolve(context);
+    syncSendRequest(context);
+    syncReadResponse(context);        
+}
+
+// resolve domain name synchronously
+void Request::syncResolve(Context &context)
+{
+    auto &url = context.url();
+    auto &sock = context.socket();
+
+    Resolver::query query(url.host(), url.port());
+         
+    ErrorCode err;
+    Resolver::iterator iter;        
+    Resolver::iterator nullIter;
+
+    for (iter = resolver_.resolve(query); iter != nullIter; ++iter)
+    {
+        auto endpoint = iter->endpoint();
+            
+        sock.open(endpoint.protocol(), err);
+        if (err)
+        {
+            continue;
+        }            
+
+        sock.connect(endpoint, err);
+        if (err)
+        {
+            sock.close();
+            continue;
+        }
+            
+        break;
+    }
+
+    if (iter == nullIter)
+    {
+        throw Exception("Resolve host " + url.host() + " error: " + err.message());                        
+    }        
+}
+
+// send HTTP request headers and body synchronously
+void Request::syncSendRequest(Context &context)
+{
+    auto &sock = context.socket();
+    auto &reqBuff = context.reqBuff();
+        
+    boost::asio::write(sock, reqBuff);
+
+    // shutdown on write
+    sock.shutdown(Socket::shutdown_send);        
+}
+
+// read HTTP response synchronously
+void Request::syncReadResponse(Context &context)
+{
+    auto &sock = context.socket();
+    auto &respBuff = context.respBuff();
+
+    // NOTE: the result may contain mutiple "\r\n\r\n"
+    boost::asio::read_until(sock, respBuff, "\r\n\r\n");
+        
+    auto str = bufferToString(respBuff);
+    auto parts = splitString(str, "\r\n\r\n", 1);
+        
+    auto headers = std::move(parts[0]);
+    context.setHeaders(std::move(headers));
+        
+    if (parts.size() == 2)
+    {
+        auto content = parts[1];
+        context.appendContent(content);
+    }
+                
+    ErrorCode readErr;
+    while (boost::asio::read(sock, respBuff,
+                             boost::asio::transfer_at_least(1),
+                             readErr))
+    {
+        if (readErr)
+        {
+            break;
+        }
+        auto content = bufferToString(respBuff);
+        context.appendContent(content);
+    }
+        
+    if (readErr && readErr != boost::asio::error::eof)
+    {
+        throw Exception("Read response error: " + readErr.message());
+    }
+}

+ 66 - 0
ThirdParty/Include/requests/requests/Response.cpp

@@ -0,0 +1,66 @@
+#include <requests/Exception.hpp>
+#include <requests/Response.hpp>
+#include <requests/Utils.hpp>
+
+#include <iostream>
+
+using namespace requests;
+
+Response::Response(const String &headers, const String &content)
+    : headersStr_(headers),
+      content_(content)
+{
+    auto lines = splitString(headersStr_, "\r\n");
+    
+    auto &statusLine = lines[0];
+    splitStatusLine(statusLine);
+    
+    for (std::size_t i = 1; i < lines.size(); ++i)
+    {
+        auto &header = lines[i];
+        splitHeader(header);
+    }
+}
+
+void Response::splitStatusLine(const String &statusLine)
+{
+    auto tokens = splitString(statusLine, " ");
+        
+    bool invalidFormat = false;
+        
+    if (tokens.size() != 3)
+    {
+        invalidFormat = true;
+    }
+    else
+    {
+        try {
+            std::stoi(tokens[1]);
+        }
+        catch (const std::invalid_argument &e)
+        {
+            invalidFormat = true;
+        }            
+    }
+
+    if (invalidFormat)
+    {
+        throw Exception("Status message in server response headers is invalid: " + statusLine);            
+    }
+
+    version_ = std::move(tokens[0]);
+    statusCode_ = std::stoi(tokens[1]);
+    statusMessage_ = std::move(tokens[2]);
+}
+
+void Response::splitHeader(const String &header)
+{
+    auto tokens = splitString(header, ": ");
+
+    if (tokens.size() != 2)
+    {
+        throw Exception("Server response contains invalid header");
+    }
+
+    headers_[tokens[0]] = tokens[1];
+}

+ 113 - 0
ThirdParty/Include/requests/requests/Url.cpp

@@ -0,0 +1,113 @@
+#include <requests/Exception.hpp>
+#include <requests/Url.hpp>
+#include <requests/Utils.hpp>
+
+using namespace requests;
+
+Url::Url(const String &url)
+    : url_(url),
+      port_("80")
+{
+    auto tokens = splitString(url, "://");        
+    if (tokens.size() != 2)
+    {
+        throw Exception("Invalid URL - no schema supplied: " + url_);                        
+    }
+        
+    schema_ = std::move(tokens[0]);
+
+    auto other = splitString(tokens[1], "/", 1);
+
+    auto hostAndPort = splitString(other[0], ":");
+    if (hostAndPort.size() != 1 && hostAndPort.size() != 2)
+    {
+        throw Exception("Invalid Host and Port: " + other[0]);
+    }        
+
+    host_ = std::move(hostAndPort[0]);
+
+    if (hostAndPort.size() == 2)
+    {
+        port_ = std::move(hostAndPort[1]);
+    }
+
+    path_ = "/";
+
+    if (other.size() == 2)
+    {
+        auto pathQueries = splitString(other[1], "?", 1);
+            
+        if (pathQueries.size() == 1)
+        {
+            queries_ = std::move(pathQueries[0]);
+        }
+        else
+        {
+            path_.append(pathQueries[0]);
+            queries_ = std::move(pathQueries[1]);
+        }
+    }
+}
+
+void Url::addQueries(const StringMap &params)
+{
+    auto queries = urlEncode(params);
+    if (hasQueries())
+    {
+        queries_.append("&").append(queries);
+    }
+    else
+    {
+        queries_ = std::move(queries);
+    }
+}
+    
+bool Url::hasQueries() const
+{
+    return !queries_.empty();        
+}
+
+Url::String Url::pathAndQueries() const
+{
+    if (hasQueries())
+    {
+        return path_ + "?" + queries_;
+    }
+    return path_;
+}
+    
+const Url::String &Url::host() const
+{
+    return host_;
+}
+
+const Url::String &Url::port() const
+{
+    return port_;
+}
+    
+const Url::String &Url::schema() const
+{
+    return schema_;
+}
+
+const Url::String &Url::path() const
+{
+    return path_;
+}
+
+const Url::String &Url::queries() const
+{
+    return queries_;
+}
+
+Url::String Url::toString() const
+{
+    return schema() + "://" + host() + pathAndQueries();
+}
+
+std::ostream &requests::operator<<(std::ostream &os, const Url &url)
+{
+    os << url.toString() << std::endl;
+    return os;
+}

+ 122 - 0
ThirdParty/Include/requests/requests/Utils.cpp

@@ -0,0 +1,122 @@
+#include <requests/Utils.hpp>
+
+#include <iostream>
+#include <iomanip>
+
+std::vector<std::string> requests::splitString(const std::string &str, const std::string &separator)
+{
+    std::vector<std::string> results;
+
+    std::size_t first = 0, last = 0;
+
+    while (first < str.size())
+    {
+        last = str.find(separator, first);
+        if (last == std::string::npos)
+        {
+            results.push_back(str.substr(first));
+            break;
+        }
+        if (last != first)
+        {
+            results.push_back(str.substr(first, last - first));            
+        }
+        first = last + separator.size();
+    }
+
+    return results;    
+}
+
+std::vector<std::string> requests::splitString(const std::string &str, const std::string &separator, std::size_t splitTimes)
+{
+    if (splitTimes == 0)
+    {
+        return { str };
+    }
+
+    std::size_t first = 0, last = 0, times = 0;
+    std::vector<std::string> results;    
+
+    while (first < str.size())
+    {
+        last = str.find(separator, first);
+        if (last == std::string::npos)
+        {
+            results.push_back(str.substr(first));
+            break;
+        }
+        if (last != first)
+        {
+            results.push_back(str.substr(first, last - first));            
+        }
+        
+        first = last + separator.size();
+
+        if (++times == splitTimes)
+        {
+            results.push_back(str.substr(first));
+            break;
+        }
+    }
+
+    return results;    
+}
+
+std::string requests::bufferToString(boost::asio::streambuf &buffer)
+{
+    auto size = buffer.size();
+    auto data = buffer.data();
+        
+    std::string str(boost::asio::buffers_begin(data), boost::asio::buffers_begin(data) + size);
+    buffer.consume(size);
+    return str;
+}
+
+std::string requests::join(const std::string &separator, const std::vector<std::string> &strs)
+{
+    std::string result;
+
+    for (std::size_t i = 0; i < strs.size(); ++i)
+    {
+        result.append(strs[i]);
+        if (i + 1 < strs.size())
+        {
+            result.append(separator);
+        }
+    }
+    
+    return result;
+}
+
+static std::string encode(const std::string &str)
+{
+    std::ostringstream escaped;
+    escaped.fill('0');
+    escaped << std::hex;
+    
+    for (auto c: str)
+    {
+        if (std::isalnum(c) || c  == '-' || c == '_' || c == '.' || c == '~')
+        {
+            escaped << c;
+            continue;
+        }
+        escaped << '%' << std::setw(2) << int((unsigned char) c);
+    }
+    return escaped.str();
+};
+
+std::string requests::urlEncode(const std::unordered_map<std::string, std::string> &params)
+{   
+    std::vector<std::string> querys;
+    
+    for (const auto &pair: params)
+    {
+        auto key = encode(pair.first);
+        auto value = encode(pair.second);
+        
+        querys.push_back(key + "=" + value);
+    }
+    
+    return join("&", querys);
+}