/** * * @file HttpRequest.h * An Tao * * Copyright 2018, An Tao. All rights reserved. * https://github.com/an-tao/drogon * Use of this source code is governed by a MIT license * that can be found in the License file. * * Drogon * */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "cache_file.h" #include "core/http_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace drogon { class HttpRequest; using HttpRequestPtr = std::shared_ptr; /** * @brief This template is used to convert a request object to a custom * type object. Users must specialize the template for a particular type. */ template T fromRequest(const HttpRequest &req) { //LOG_ERROR << "You must specialize the fromRequest template for the type of " // << DrClassMap::demangle(typeid(T).name()); exit(1); } /** * @brief This template is used to create a request object from a custom * type object by calling the newCustomHttpRequest(). Users must specialize * the template for a particular type. */ template HttpRequestPtr toRequest(T &&) { //LOG_ERROR << "You must specialize the toRequest template for the type of " // << DrClassMap::demangle(typeid(T).name()); exit(1); } template <> HttpRequestPtr toRequest(const Json::Value &pJson); template <> HttpRequestPtr toRequest(Json::Value &&pJson); template <> inline HttpRequestPtr toRequest(Json::Value &pJson) { return toRequest((const Json::Value &)pJson); } template <> std::shared_ptr fromRequest(const HttpRequest &req); /// Abstract class for webapp developer to get or set the Http request; class HttpRequest { public: friend class HttpRequestParser; explicit HttpRequest(trantor::EventLoop *loop) : creationDate_(trantor::Date::now()), loop_(loop) { } void reset() { method_ = Invalid; version_ = Version::kUnknown; flagForParsingJson_ = false; headers_.clear(); cookies_.clear(); flagForParsingParameters_ = false; path_.clear(); matchedPathPattern_ = ""; query_.clear(); parameters_.clear(); jsonPtr_.reset(); sessionPtr_.reset(); attributesPtr_.reset(); cacheFilePtr_.reset(); expectPtr_.reset(); content_.clear(); contentType_ = CT_TEXT_PLAIN; flagForParsingContentType_ = false; contentTypeString_.clear(); keepAlive_ = true; jsonParsingErrorPtr_.reset(); } trantor::EventLoop *getLoop() { return loop_; } void setVersion(Version v) { version_ = v; if (version_ == Version::kHttp10) { keepAlive_ = false; } } void setPath(const char *start, const char *end) { if (utils::needUrlDecoding(start, end)) { path_ = utils::urlDecode(start, end); } else { path_.append(start, end); } } virtual void setPath(const std::string &path) { path_ = path; } /** * @brief This template enables implicit type conversion. For using this * template, user must specialize the fromRequest template. For example a * shared_ptr specialization version is available above, so * we can use the following code to get a json object: * @code std::shared_ptr jsonPtr = *requestPtr; @endcode * With this template, user can use their favorite JSON library instead of * the default jsoncpp library or convert the request to an object of any * custom type. */ template operator T() const { return fromRequest(*this); } /** * @brief This template enables explicit type conversion, see the above * template. */ template T as() const { return fromRequest(*this); } bool setMethod(const char *start, const char *end); /// Return the method string of the request, such as GET, POST, etc. virtual const char *methodString() const; const char *getMethodString() const { return methodString(); } /// Return the enum type method of the request. virtual HttpMethod method() const { return method_; } HttpMethod getMethod() const { return method(); } /// Get the header string identified by the field parameter virtual const std::string &getHeader(const std::string &field) const { auto lowField = field; std::transform(lowField.begin(), lowField.end(), lowField.begin(), tolower); return getHeaderBy(lowField); } /// Get the header string identified by the field parameter virtual const std::string &getHeader(std::string &&field) const { std::transform(field.begin(), field.end(), field.begin(), tolower); return getHeaderBy(field); } /// Set the header string identified by the field parameter virtual void addHeader(const std::string &key, const std::string &value) { headers_[key] = value; } /// Get the cookie string identified by the field parameter virtual const std::string &getCookie(const std::string &field) const { const static std::string defaultVal; auto it = cookies_.find(field); if (it != cookies_.end()) { return it->second; } return defaultVal; } /// Get all headers of the request virtual const std::unordered_map &headers() const { return headers_; } /// Get all headers of the request const std::unordered_map &getHeaders() const { return headers(); } /// Get all cookies of the request virtual const std::unordered_map &cookies() const { return cookies_; } /// Get all cookies of the request const std::unordered_map &getCookies() const { return cookies(); } /// Get the query string of the request. /** * The query string is the substring after the '?' in the URL string. */ virtual const std::string &query() const { return query_; } /// Get the query string of the request. const std::string &getQuery() const { return query(); } /// Get the content string of the request, which is the body part of the /// request. string_view body() const { return string_view(bodyData(), bodyLength()); } /// Get the content string of the request, which is the body part of the /// request. string_view getBody() const { return body(); } void setQuery(const char *start, const char *end) { query_.assign(start, end); } void setQuery(const std::string &query) { query_ = query; } string_view bodyView() const { if (cacheFilePtr_) { return cacheFilePtr_->getStringView(); } return content_; } virtual const char *bodyData() const { if (cacheFilePtr_) { return cacheFilePtr_->getStringView().data(); } return content_.data(); } virtual size_t bodyLength() const { if (cacheFilePtr_) { return cacheFilePtr_->getStringView().length(); } return content_.length(); } void appendToBody(const char *data, size_t length); void reserveBodySize(size_t length); string_view queryView() const { return query_; } string_view contentView() const { if (cacheFilePtr_) return cacheFilePtr_->getStringView(); return content_; } /// Set the content string of the request. virtual void setBody(const std::string &body) { content_ = body; } /// Set the content string of the request. virtual void setBody(std::string &&body) { content_ = std::move(body); } /// Get the path of the request. virtual const std::string &path() const { return path_; } /// Get the path of the request. const std::string &getPath() const { return path(); } /// Get the matched path pattern after routing string_view getMatchedPathPattern() const { return matchedPathPattern(); } /// Get the matched path pattern after routing string_view matchedPathPattern() const { return string_view(matchedPathPatternData(), matchedPathPatternLength()); } virtual const char *matchedPathPatternData() const { return matchedPathPattern_.data(); } virtual size_t matchedPathPatternLength() const { return matchedPathPattern_.length(); } void setMatchedPathPattern(const std::string &pathPattern) { matchedPathPattern_ = pathPattern; } const std::string &expect() const { const static std::string none{ "" }; if (expectPtr_) return *expectPtr_; return none; } bool keepAlive() const { return keepAlive_; } /// Return the enum type version of the request. /** * kHttp10 means Http version is 1.0 * kHttp11 means Http verison is 1.1 */ virtual Version version() const { return version_; } /// Return the enum type version of the request. Version getVersion() const { return version(); } /// Get the session to which the request belongs. virtual SessionPtr session() const { return sessionPtr_; } void setSession(const SessionPtr &session) { sessionPtr_ = session; } /// Get the session to which the request belongs. SessionPtr getSession() const { return session(); } /// Get the attributes store, users can add/get any type of data to/from /// this store virtual const AttributesPtr &attributes() const { if (!attributesPtr_) { attributesPtr_ = std::make_shared(); } return attributesPtr_; } /// Get the attributes store, users can add/get any type of data to/from /// this store const AttributesPtr &getAttributes() const { return attributes(); } /// Get parameters of the request. virtual const std::unordered_map ¶meters() const { parseParametersOnce(); return parameters_; } /// Get parameters of the request. const std::unordered_map &getParameters() const { return parameters(); } /// Get a parameter identified by the @param key virtual const std::string &getParameter( const std::string &key) const { const static std::string defaultVal; parseParametersOnce(); auto iter = parameters_.find(key); if (iter != parameters_.end()) return iter->second; return defaultVal; } /// Return the remote IP address and port virtual const trantor::InetAddress &peerAddr() const { return peer_; } const trantor::InetAddress &getPeerAddr() const { return peerAddr(); } /// Return the local IP address and port virtual const trantor::InetAddress &localAddr() const { return local_; } const trantor::InetAddress &getLocalAddr() const { return localAddr(); } /// Return the creation timestamp set by the framework. virtual const trantor::Date &creationDate() const { return creationDate_; } const trantor::Date &getCreationDate() const { return creationDate(); } void setCreationDate(const trantor::Date &date) { creationDate_ = date; } void setPeerAddr(const trantor::InetAddress &peer) { peer_ = peer; } void setLocalAddr(const trantor::InetAddress &local) { local_ = local; } void addHeader(const char *start, const char *colon, const char *end); const std::string &getHeaderBy(const std::string &lowerField) const { const static std::string defaultVal; auto it = headers_.find(lowerField); if (it != headers_.end()) { return it->second; } return defaultVal; } /// Get the Json object of the request /** * The content type of the request must be 'application/json', and the query * string (the part after the question mark in the URI) must be empty, * otherwise the method returns an empty shared_ptr object. */ virtual const std::shared_ptr &jsonObject() const { // Not multi-thread safe but good, because we basically call this // function in a single thread if (!flagForParsingJson_) { flagForParsingJson_ = true; parseJson(); } return jsonPtr_; } /// Get the Json object of the request const std::shared_ptr &getJsonObject() const { return jsonObject(); } /** * @brief Get the error message of parsing the JSON body received from peer. * This method usually is called after getting a empty shared_ptr object * by the getJsonObject() method. * * @return const std::string& The error message. An empty string is returned * when no error occurs. */ virtual const std::string &getJsonError() const { const static std::string none{ "" }; if (jsonParsingErrorPtr_) return *jsonParsingErrorPtr_; return none; } /// Get the content type virtual ContentType contentType() const { if (!flagForParsingContentType_) { flagForParsingContentType_ = true; auto &contentTypeString = getHeaderBy("content-type"); if (contentTypeString == "") { contentType_ = CT_NONE; } else { auto pos = contentTypeString.find(';'); if (pos != std::string::npos) { contentType_ = parseContentType( string_view(contentTypeString.data(), pos)); } else { contentType_ = parseContentType(string_view(contentTypeString)); } } } return contentType_; } ContentType getContentType() const { return contentType(); } /// Set the Http method virtual void setMethod(const HttpMethod method) { method_ = method; return; } /// Set the path of the request void setSecure(bool secure) { isOnSecureConnection_ = secure; } /// Set the parameter of the request virtual void setParameter(const std::string &key, const std::string &value) { flagForParsingParameters_ = true; parameters_[key] = value; } const std::string &getContent() const { return content_; } void swap(HttpRequest &that) noexcept; void setContent(const std::string &content) { content_ = content; } /// Set or get the content type virtual void setContentTypeCode(const ContentType type) { contentType_ = type; flagForParsingContentType_ = true; auto &typeStr = webContentTypeToString(type); setContentType(std::string(typeStr.data(), typeStr.length())); } /// Set the request content-type string, The string /// must contain the header name and CRLF. /// For example, "content-type: text/plain\r\n" virtual void setCustomContentTypeString(const std::string &type) { contentType_ = CT_NONE; flagForParsingContentType_ = true; contentTypeString_ = type; } // virtual void setContentTypeCodeAndCharacterSet(ContentType type, const // std::string &charSet = "utf-8") // { // contentType_ = type; // setContentType(webContentTypeAndCharsetToString(type, charSet)); // } /// Add a cookie virtual void addCookie(const std::string &key, const std::string &value) { cookies_[key] = value; } /** * @brief Set the request object to the pass-through mode or not. It's not * by default when a new request object is created. * In pass-through mode, no addtional headers (including server, date, * content-type and content-length, etc.) are added to the request. This * mode is useful for some applications such as a proxy. * * @param flag */ virtual void setPassThrough(bool flag) { passThrough_ = flag; } bool passThrough() const { return passThrough_; } void appendToBuffer(trantor::MsgBuffer *output) const; virtual ~HttpRequest(); /// The following methods are a series of factory methods that help users /// create request objects. /// Create a normal request with http method Get and version Http1.1. static HttpRequestPtr newHttpRequest(); /// Create a http request with: /// Method: Get /// Version: Http1.1 /// Content type: application/json, the @param data is serialized into the /// content of the request. static HttpRequestPtr newHttpJsonRequest(const Json::Value &data); /// Create a http request with: /// Method: Post /// Version: Http1.1 /// Content type: application/x-www-form-urlencoded static HttpRequestPtr newHttpFormPostRequest(); /// Create a http file upload request with: /// Method: Post /// Version: Http1.1 /// Content type: multipart/form-data /// The @param files represents pload files which are transferred to the /// server via the multipart/form-data format static HttpRequestPtr newFileUploadRequest( const std::vector &files); /** * @brief Create a custom HTTP request object. For using this template, * users must specialize the toRequest template. */ template static HttpRequestPtr newCustomHttpRequest(T &&obj) { return toRequest(std::forward(obj)); } virtual bool isOnSecureConnection() const noexcept { return isOnSecureConnection_; } protected: void setContentType(const std::string &contentType) { contentTypeString_ = contentType; } void setContentType(std::string &&contentType) { contentTypeString_ = std::move(contentType); } private: void parseParameters() const; void parseParametersOnce() const { // Not multi-thread safe but good, because we basically call this // function in a single thread if (!flagForParsingParameters_) { flagForParsingParameters_ = true; parseParameters(); } } void createTmpFile(); void parseJson() const; mutable bool flagForParsingParameters_{ false }; mutable bool flagForParsingJson_{ false }; HttpMethod method_{ Invalid }; Version version_{ Version::kUnknown }; std::string path_; string_view matchedPathPattern_{ "" }; std::string query_; std::unordered_map headers_; std::unordered_map cookies_; mutable std::unordered_map parameters_; mutable std::shared_ptr jsonPtr_; SessionPtr sessionPtr_; mutable AttributesPtr attributesPtr_; trantor::InetAddress peer_; trantor::InetAddress local_; trantor::Date creationDate_; std::unique_ptr cacheFilePtr_; mutable std::unique_ptr jsonParsingErrorPtr_; std::unique_ptr expectPtr_; bool keepAlive_{ true }; bool isOnSecureConnection_{ false }; bool passThrough_{ false }; protected: std::string content_; trantor::EventLoop *loop_; mutable ContentType contentType_{ CT_TEXT_PLAIN }; mutable bool flagForParsingContentType_{ false }; std::string contentTypeString_; }; template <> inline HttpRequestPtr toRequest(const Json::Value &pJson) { return HttpRequest::newHttpJsonRequest(pJson); } template <> inline HttpRequestPtr toRequest(Json::Value &&pJson) { return HttpRequest::newHttpJsonRequest(std::move(pJson)); } template <> inline std::shared_ptr fromRequest(const HttpRequest &req) { return req.getJsonObject(); } using HttpRequestPtr = std::shared_ptr; inline void swap(HttpRequest &one, HttpRequest &two) noexcept { one.swap(two); } } // namespace drogon