]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #include <boost/algorithm/string/predicate.hpp> | |
5 | #include <boost/asio/write.hpp> | |
6 | #include <beast/http/read.hpp> | |
7 | ||
8 | #include "rgw_asio_client.h" | |
9 | ||
10 | #define dout_context g_ceph_context | |
11 | #define dout_subsys ceph_subsys_rgw | |
12 | ||
13 | #undef dout_prefix | |
14 | #define dout_prefix (*_dout << "asio: ") | |
15 | ||
16 | using namespace rgw::asio; | |
17 | ||
18 | ClientIO::ClientIO(tcp::socket& socket, | |
19 | parser_type& parser, | |
20 | beast::flat_streambuf& buffer) | |
21 | : socket(socket), parser(parser), buffer(buffer), txbuf(*this) | |
22 | { | |
23 | } | |
24 | ||
25 | ClientIO::~ClientIO() = default; | |
26 | ||
27 | void ClientIO::init_env(CephContext *cct) | |
28 | { | |
29 | env.init(cct); | |
30 | ||
31 | const auto& request = parser.get(); | |
32 | const auto& headers = request.fields; | |
33 | for (auto header = headers.begin(); header != headers.end(); ++header) { | |
34 | const auto& name = header->name(); | |
35 | const auto& value = header->value(); | |
36 | ||
37 | if (boost::algorithm::iequals(name, "content-length")) { | |
38 | env.set("CONTENT_LENGTH", value); | |
39 | continue; | |
40 | } | |
41 | if (boost::algorithm::iequals(name, "content-type")) { | |
42 | env.set("CONTENT_TYPE", value); | |
43 | continue; | |
44 | } | |
45 | if (boost::algorithm::iequals(name, "connection")) { | |
46 | conn_keepalive = boost::algorithm::iequals(value, "keep-alive"); | |
47 | conn_close = boost::algorithm::iequals(value, "close"); | |
48 | } | |
49 | ||
50 | static const boost::string_ref HTTP_{"HTTP_"}; | |
51 | ||
52 | char buf[name.size() + HTTP_.size() + 1]; | |
53 | auto dest = std::copy(std::begin(HTTP_), std::end(HTTP_), buf); | |
54 | for (auto src = name.begin(); src != name.end(); ++src, ++dest) { | |
55 | if (*src == '-') { | |
56 | *dest = '_'; | |
57 | } else { | |
58 | *dest = std::toupper(*src); | |
59 | } | |
60 | } | |
61 | *dest = '\0'; | |
62 | ||
63 | env.set(buf, value); | |
64 | } | |
65 | ||
66 | env.set("REQUEST_METHOD", request.method); | |
67 | ||
68 | // split uri from query | |
69 | auto url = boost::string_ref{request.url}; | |
70 | auto pos = url.find('?'); | |
71 | auto query = url.substr(pos + 1); | |
72 | url = url.substr(0, pos); | |
73 | ||
74 | env.set("REQUEST_URI", url); | |
75 | env.set("QUERY_STRING", query); | |
76 | env.set("SCRIPT_URI", url); /* FIXME */ | |
77 | ||
78 | char port_buf[16]; | |
79 | snprintf(port_buf, sizeof(port_buf), "%d", socket.local_endpoint().port()); | |
80 | env.set("SERVER_PORT", port_buf); | |
81 | // TODO: set SERVER_PORT_SECURE if using ssl | |
82 | // TODO: set REMOTE_USER if authenticated | |
83 | } | |
84 | ||
85 | size_t ClientIO::write_data(const char* buf, size_t len) | |
86 | { | |
87 | boost::system::error_code ec; | |
88 | auto bytes = boost::asio::write(socket, boost::asio::buffer(buf, len), ec); | |
89 | if (ec) { | |
90 | derr << "write_data failed: " << ec.message() << dendl; | |
91 | throw rgw::io::Exception(ec.value(), std::system_category()); | |
92 | } | |
93 | /* According to the documentation of boost::asio::write if there is | |
94 | * no error (signalised by ec), then bytes == len. We don't need to | |
95 | * take care of partial writes in such situation. */ | |
96 | return bytes; | |
97 | } | |
98 | ||
99 | size_t ClientIO::read_data(char* buf, size_t max) | |
100 | { | |
101 | auto& message = parser.get(); | |
102 | auto& body_remaining = message.body; | |
103 | body_remaining = boost::asio::mutable_buffer{buf, max}; | |
104 | ||
105 | boost::system::error_code ec; | |
106 | ||
107 | dout(30) << this << " read_data for " << max << " with " | |
108 | << buffer.size() << " bytes buffered" << dendl; | |
109 | ||
110 | while (boost::asio::buffer_size(body_remaining) && !parser.is_complete()) { | |
111 | auto bytes = beast::http::read_some(socket, buffer, parser, ec); | |
112 | buffer.consume(bytes); | |
113 | if (ec == boost::asio::error::connection_reset || | |
114 | ec == boost::asio::error::eof || | |
115 | ec == beast::http::error::partial_message) { | |
116 | break; | |
117 | } | |
118 | if (ec) { | |
119 | derr << "failed to read body: " << ec.message() << dendl; | |
120 | throw rgw::io::Exception(ec.value(), std::system_category()); | |
121 | } | |
122 | } | |
123 | return max - boost::asio::buffer_size(body_remaining); | |
124 | } | |
125 | ||
126 | size_t ClientIO::complete_request() | |
127 | { | |
128 | return 0; | |
129 | } | |
130 | ||
131 | void ClientIO::flush() | |
132 | { | |
133 | txbuf.pubsync(); | |
134 | } | |
135 | ||
136 | size_t ClientIO::send_status(int status, const char* status_name) | |
137 | { | |
138 | static constexpr size_t STATUS_BUF_SIZE = 128; | |
139 | ||
140 | char statusbuf[STATUS_BUF_SIZE]; | |
141 | const auto statuslen = snprintf(statusbuf, sizeof(statusbuf), | |
142 | "HTTP/1.1 %d %s\r\n", status, status_name); | |
143 | ||
144 | return txbuf.sputn(statusbuf, statuslen); | |
145 | } | |
146 | ||
147 | size_t ClientIO::send_100_continue() | |
148 | { | |
149 | const char HTTTP_100_CONTINUE[] = "HTTP/1.1 100 CONTINUE\r\n\r\n"; | |
150 | const size_t sent = txbuf.sputn(HTTTP_100_CONTINUE, | |
151 | sizeof(HTTTP_100_CONTINUE) - 1); | |
152 | flush(); | |
153 | return sent; | |
154 | } | |
155 | ||
156 | static constexpr size_t TIME_BUF_SIZE = 128; | |
157 | static size_t dump_date_header(char (×tr)[TIME_BUF_SIZE]) | |
158 | { | |
159 | const time_t gtime = time(nullptr); | |
160 | struct tm result; | |
161 | struct tm const * const tmp = gmtime_r(>ime, &result); | |
162 | if (tmp == nullptr) { | |
163 | return 0; | |
164 | } | |
165 | return strftime(timestr, sizeof(timestr), | |
166 | "Date: %a, %d %b %Y %H:%M:%S %Z\r\n", tmp); | |
167 | } | |
168 | ||
169 | size_t ClientIO::complete_header() | |
170 | { | |
171 | size_t sent = 0; | |
172 | ||
173 | char timestr[TIME_BUF_SIZE]; | |
174 | if (dump_date_header(timestr)) { | |
175 | sent += txbuf.sputn(timestr, strlen(timestr)); | |
176 | } | |
177 | ||
178 | if (conn_keepalive) { | |
179 | constexpr char CONN_KEEP_ALIVE[] = "Connection: Keep-Alive\r\n"; | |
180 | sent += txbuf.sputn(CONN_KEEP_ALIVE, sizeof(CONN_KEEP_ALIVE) - 1); | |
181 | } else if (conn_close) { | |
182 | constexpr char CONN_KEEP_CLOSE[] = "Connection: close\r\n"; | |
183 | sent += txbuf.sputn(CONN_KEEP_CLOSE, sizeof(CONN_KEEP_CLOSE) - 1); | |
184 | } | |
185 | ||
186 | constexpr char HEADER_END[] = "\r\n"; | |
187 | sent += txbuf.sputn(HEADER_END, sizeof(HEADER_END) - 1); | |
188 | ||
189 | flush(); | |
190 | return sent; | |
191 | } | |
192 | ||
193 | size_t ClientIO::send_header(const boost::string_ref& name, | |
194 | const boost::string_ref& value) | |
195 | { | |
196 | static constexpr char HEADER_SEP[] = ": "; | |
197 | static constexpr char HEADER_END[] = "\r\n"; | |
198 | ||
199 | size_t sent = 0; | |
200 | ||
201 | sent += txbuf.sputn(name.data(), name.length()); | |
202 | sent += txbuf.sputn(HEADER_SEP, sizeof(HEADER_SEP) - 1); | |
203 | sent += txbuf.sputn(value.data(), value.length()); | |
204 | sent += txbuf.sputn(HEADER_END, sizeof(HEADER_END) - 1); | |
205 | ||
206 | return sent; | |
207 | } | |
208 | ||
209 | size_t ClientIO::send_content_length(uint64_t len) | |
210 | { | |
211 | static constexpr size_t CONLEN_BUF_SIZE = 128; | |
212 | ||
213 | char sizebuf[CONLEN_BUF_SIZE]; | |
214 | const auto sizelen = snprintf(sizebuf, sizeof(sizebuf), | |
215 | "Content-Length: %" PRIu64 "\r\n", len); | |
216 | ||
217 | return txbuf.sputn(sizebuf, sizelen); | |
218 | } |