aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/neh/http_common.cpp
diff options
context:
space:
mode:
authormonster <monster@ydb.tech>2022-07-07 14:41:37 +0300
committermonster <monster@ydb.tech>2022-07-07 14:41:37 +0300
commit06e5c21a835c0e923506c4ff27929f34e00761c2 (patch)
tree75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /library/cpp/neh/http_common.cpp
parent03f024c4412e3aa613bb543cf1660176320ba8f4 (diff)
downloadydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz
fix ya.make
Diffstat (limited to 'library/cpp/neh/http_common.cpp')
-rw-r--r--library/cpp/neh/http_common.cpp235
1 files changed, 235 insertions, 0 deletions
diff --git a/library/cpp/neh/http_common.cpp b/library/cpp/neh/http_common.cpp
new file mode 100644
index 0000000000..7ae466c31a
--- /dev/null
+++ b/library/cpp/neh/http_common.cpp
@@ -0,0 +1,235 @@
+#include "http_common.h"
+
+#include "location.h"
+#include "http_headers.h"
+
+#include <util/generic/array_ref.h>
+#include <util/generic/singleton.h>
+#include <util/stream/length.h>
+#include <util/stream/null.h>
+#include <util/stream/str.h>
+#include <util/string/ascii.h>
+
+using NNeh::NHttp::ERequestType;
+
+namespace {
+ bool IsEmpty(const TStringBuf url) {
+ return url.empty();
+ }
+
+ void WriteImpl(const TStringBuf url, IOutputStream& out) {
+ out << url;
+ }
+
+ bool IsEmpty(const TConstArrayRef<TString> urlParts) {
+ return urlParts.empty();
+ }
+
+ void WriteImpl(const TConstArrayRef<TString> urlParts, IOutputStream& out) {
+ NNeh::NHttp::JoinUrlParts(urlParts, out);
+ }
+
+ template <typename T>
+ size_t GetLength(const T& urlParts) {
+ TCountingOutput out(&Cnull);
+ WriteImpl(urlParts, out);
+ return out.Counter();
+ }
+
+ template <typename T>
+ void WriteUrl(const T& urlParts, IOutputStream& out) {
+ if (!IsEmpty(urlParts)) {
+ out << '?';
+ WriteImpl(urlParts, out);
+ }
+ }
+}
+
+namespace NNeh {
+ namespace NHttp {
+ size_t GetUrlPartsLength(const TConstArrayRef<TString> urlParts) {
+ size_t res = 0;
+
+ for (const auto& u : urlParts) {
+ res += u.length();
+ }
+
+ if (urlParts.size() > 0) {
+ res += urlParts.size() - 1; //'&' between parts
+ }
+
+ return res;
+ }
+
+ void JoinUrlParts(const TConstArrayRef<TString> urlParts, IOutputStream& out) {
+ if (urlParts.empty()) {
+ return;
+ }
+
+ out << urlParts[0];
+
+ for (size_t i = 1; i < urlParts.size(); ++i) {
+ out << '&' << urlParts[i];
+ }
+ }
+
+ void WriteUrlParts(const TConstArrayRef<TString> urlParts, IOutputStream& out) {
+ WriteUrl(urlParts, out);
+ }
+ }
+}
+
+namespace {
+ const TStringBuf schemeHttps = "https";
+ const TStringBuf schemeHttp = "http";
+ const TStringBuf schemeHttp2 = "http2";
+ const TStringBuf schemePost = "post";
+ const TStringBuf schemePosts = "posts";
+ const TStringBuf schemePost2 = "post2";
+ const TStringBuf schemeFull = "full";
+ const TStringBuf schemeFulls = "fulls";
+ const TStringBuf schemeHttpUnix = "http+unix";
+ const TStringBuf schemePostUnix = "post+unix";
+
+ /*
+ @brief SafeWriteHeaders write headers from hdrs to out with some checks:
+ - filter out Content-Lenthgh because we'll add it ourselfs later.
+
+ @todo ensure headers right formatted (now receive from perl report bad format headers)
+ */
+ void SafeWriteHeaders(IOutputStream& out, TStringBuf hdrs) {
+ NNeh::NHttp::THeaderSplitter splitter(hdrs);
+ TStringBuf msgHdr;
+ while (splitter.Next(msgHdr)) {
+ if (!AsciiHasPrefixIgnoreCase(msgHdr, TStringBuf("Content-Length"))) {
+ out << msgHdr << TStringBuf("\r\n");
+ }
+ }
+ }
+
+ template <typename T, typename W>
+ TString BuildRequest(const NNeh::TParsedLocation& loc, const T& urlParams, const TStringBuf headers, const W& content, const TStringBuf contentType, ERequestType requestType, NNeh::NHttp::ERequestFlags requestFlags) {
+ const bool isAbsoluteUri = requestFlags.HasFlags(NNeh::NHttp::ERequestFlag::AbsoluteUri);
+
+ const auto contentLength = GetLength(content);
+ TStringStream out;
+ out.Reserve(loc.Service.length() + loc.Host.length() + GetLength(urlParams) + headers.length() + contentType.length() + contentLength + (isAbsoluteUri ? (loc.Host.length() + 13) : 0) // 13 - is a max port number length + scheme length
+ + 96); //just some extra space
+
+ Y_ASSERT(requestType != ERequestType::Any);
+ out << requestType;
+ out << ' ';
+ if (isAbsoluteUri) {
+ out << loc.Scheme << TStringBuf("://") << loc.Host;
+ if (loc.Port) {
+ out << ':' << loc.Port;
+ }
+ }
+ out << '/' << loc.Service;
+
+ WriteUrl(urlParams, out);
+ out << TStringBuf(" HTTP/1.1\r\n");
+
+ NNeh::NHttp::WriteHostHeaderIfNot(out, loc.Host, loc.Port, headers);
+ SafeWriteHeaders(out, headers);
+ if (!IsEmpty(content)) {
+ if (!!contentType && headers.find(TStringBuf("Content-Type:")) == TString::npos) {
+ out << TStringBuf("Content-Type: ") << contentType << TStringBuf("\r\n");
+ }
+ out << TStringBuf("Content-Length: ") << contentLength << TStringBuf("\r\n");
+ out << TStringBuf("\r\n");
+ WriteImpl(content, out);
+ } else {
+ out << TStringBuf("\r\n");
+ }
+ return out.Str();
+ }
+
+ bool NeedGetRequestFor(TStringBuf scheme) {
+ return scheme == schemeHttp2 || scheme == schemeHttp || scheme == schemeHttps || scheme == schemeHttpUnix;
+ }
+
+ bool NeedPostRequestFor(TStringBuf scheme) {
+ return scheme == schemePost2 || scheme == schemePost || scheme == schemePosts || scheme == schemePostUnix;
+ }
+
+ inline ERequestType ChooseReqType(ERequestType userReqType, ERequestType defaultReqType) {
+ Y_ASSERT(defaultReqType != ERequestType::Any);
+ return userReqType != ERequestType::Any ? userReqType : defaultReqType;
+ }
+}
+
+namespace NNeh {
+ namespace NHttp {
+ const TStringBuf DefaultContentType = "application/x-www-form-urlencoded";
+
+ template <typename T>
+ bool MakeFullRequestImpl(TMessage& msg, const TStringBuf proxy, const T& urlParams, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) {
+ NNeh::TParsedLocation loc(msg.Addr);
+
+ if (content.size()) {
+ //content MUST be placed inside POST requests
+ if (!IsEmpty(urlParams)) {
+ if (NeedGetRequestFor(loc.Scheme)) {
+ msg.Data = BuildRequest(loc, urlParams, headers, content, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags);
+ } else {
+ // cannot place in first header line potentially unsafe data from POST message
+ // (can contain forbidden for url-path characters)
+ // so support such mutation only for GET requests
+ return false;
+ }
+ } else {
+ if (NeedGetRequestFor(loc.Scheme) || NeedPostRequestFor(loc.Scheme)) {
+ msg.Data = BuildRequest(loc, urlParams, headers, content, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags);
+ } else {
+ return false;
+ }
+ }
+ } else {
+ if (NeedGetRequestFor(loc.Scheme)) {
+ msg.Data = BuildRequest(loc, urlParams, headers, "", "", ChooseReqType(reqType, ERequestType::Get), reqFlags);
+ } else if (NeedPostRequestFor(loc.Scheme)) {
+ msg.Data = BuildRequest(loc, TString(), headers, urlParams, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags);
+ } else {
+ return false;
+ }
+ }
+
+ if (proxy.IsInited()) {
+ loc = NNeh::TParsedLocation(proxy);
+ msg.Addr = proxy;
+ }
+
+ TString schemePostfix = "";
+ if (loc.Scheme.EndsWith("+unix")) {
+ schemePostfix = "+unix";
+ }
+
+ // ugly but still... https2 will break it :(
+ if ('s' == loc.Scheme[loc.Scheme.size() - 1]) {
+ msg.Addr.replace(0, loc.Scheme.size(), schemeFulls + schemePostfix);
+ } else {
+ msg.Addr.replace(0, loc.Scheme.size(), schemeFull + schemePostfix);
+ }
+
+ return true;
+ }
+
+ bool MakeFullRequest(TMessage& msg, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) {
+ return MakeFullRequestImpl(msg, {}, msg.Data, headers, content, contentType, reqType, reqFlags);
+ }
+
+ bool MakeFullRequest(TMessage& msg, const TConstArrayRef<TString> urlParts, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) {
+ return MakeFullRequestImpl(msg, {}, urlParts, headers, content, contentType, reqType, reqFlags);
+ }
+
+ bool MakeFullProxyRequest(TMessage& msg, TStringBuf proxyAddr, TStringBuf headers, TStringBuf content, TStringBuf contentType, ERequestType reqType, ERequestFlags flags) {
+ return MakeFullRequestImpl(msg, proxyAddr, msg.Data, headers, content, contentType, reqType, flags | ERequestFlag::AbsoluteUri);
+ }
+
+ bool IsHttpScheme(TStringBuf scheme) {
+ return NeedGetRequestFor(scheme) || NeedPostRequestFor(scheme);
+ }
+ }
+}
+