| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include "rest_client.hpp" |
| #include "test_utils.hpp" |
| #include "tlog.hpp" |
| |
| #include "address.hpp" |
| |
| #include <sstream> |
| |
| #define HTTP_EOL "\r\n" |
| #define OUTPUT_BUFFER_SIZE 10240ul |
| |
| using namespace datastax::internal::core; |
| |
| // Static initializations |
| uv_buf_t RestClient::write_buf_; |
| uv_write_t RestClient::write_req_; |
| |
| /** |
| + * HTTP request |
| + */ |
| struct HttpRequest { |
| /** |
| * HTTP message to submit to the REST server |
| */ |
| std::string message; |
| /** |
| * libuv loop (for processing errors) |
| */ |
| uv_loop_t* loop; |
| /** |
| * HTTP response for sent request |
| */ |
| Response response; |
| }; |
| |
| const Response RestClient::send_request(const Request& request) { |
| // Initialize the loop |
| uv_loop_t loop; |
| int error_code = uv_loop_init(&loop); |
| if (error_code != 0) { |
| throw Exception("Unable to Send Request: " + std::string(uv_strerror(error_code))); |
| }; |
| |
| // Initialize the HTTP request |
| HttpRequest http_request; |
| http_request.message = generate_http_message(request); |
| http_request.loop = &loop; |
| |
| // Create the IPv4 socket address |
| const Address address(request.address.c_str(), static_cast<int>(request.port)); |
| |
| // Initialize the client TCP request |
| uv_tcp_t tcp; |
| tcp.data = &http_request; |
| error_code = uv_tcp_init(&loop, &tcp); |
| if (error_code != 0) { |
| TEST_LOG_ERROR("Unable to Initialize TCP Request: " + std::string(uv_strerror(error_code))); |
| } |
| error_code = uv_tcp_keepalive(&tcp, 1, 60); |
| if (error_code != 0) { |
| TEST_LOG_ERROR("Unable to Set TCP KeepAlive: " + std::string(uv_strerror(error_code))); |
| } |
| |
| // Start the request and attach the HTTP request to send to the REST server |
| uv_connect_t connect; |
| connect.data = &http_request; |
| Address::SocketStorage storage; |
| uv_tcp_connect(&connect, &tcp, address.to_sockaddr(&storage), handle_connected); |
| |
| uv_run(&loop, UV_RUN_DEFAULT); |
| uv_loop_close(&loop); |
| |
| // Return the response from the request |
| return http_request.response; |
| } |
| |
| void RestClient::handle_allocation(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buffer) { |
| buffer->base = new char[OUTPUT_BUFFER_SIZE]; |
| buffer->len = OUTPUT_BUFFER_SIZE; |
| } |
| |
| void RestClient::handle_connected(uv_connect_t* req, int status) { |
| HttpRequest* request = static_cast<HttpRequest*>(req->data); |
| |
| if (status < 0) { |
| TEST_LOG_ERROR("Unable to Connect to HTTP Server: " << uv_strerror(status)); |
| uv_close(reinterpret_cast<uv_handle_t*>(req->handle), NULL); |
| } else { |
| // Create the buffer to write to the stream |
| write_buf_.base = const_cast<char*>(request->message.c_str()); |
| write_buf_.len = request->message.size(); |
| |
| // Send the HTTP request |
| uv_read_start(req->handle, handle_allocation, handle_response); |
| uv_write(&write_req_, req->handle, &write_buf_, 1, NULL); |
| } |
| } |
| |
| void RestClient::handle_response(uv_stream_t* stream, ssize_t buffer_length, |
| const uv_buf_t* buffer) { |
| HttpRequest* request = static_cast<HttpRequest*>(stream->data); |
| |
| if (buffer_length > 0) { |
| // Process the buffer and log it |
| std::string server_response = std::string(buffer->base, buffer_length); |
| TEST_LOG(test::Utils::trim(server_response)); |
| |
| // Parse the status code and content of the response |
| std::istringstream lines(server_response); |
| std::string current_line; |
| while (std::getline(lines, current_line)) { |
| // Determine if the status code should be assigned in the response |
| if (current_line.compare(0, 4, "HTTP") == 0) { |
| // Status-Line = HTTP-Version <SPC> Status-Code <SPC> Reason-Phrase |
| std::stringstream status_code; |
| status_code << test::Utils::explode(current_line, ' ').at(1); |
| if ((status_code >> request->response.status_code).fail()) { |
| TEST_LOG_ERROR("Unable to Determine Status Code: " << current_line); |
| } |
| } else if (current_line.compare(0, 1, "\r") == 0) { |
| while (std::getline(lines, current_line)) { |
| request->response.message.append(test::Utils::trim(current_line)); |
| } |
| } else if (!request->response.message.empty()) { |
| request->response.message.append(test::Utils::trim(current_line)); |
| } |
| } |
| } else if (buffer_length < 0) { |
| uv_close(reinterpret_cast<uv_handle_t*>(stream), NULL); |
| } |
| |
| // Clean up the memory allocated |
| delete[] buffer->base; |
| } |
| |
| const std::string RestClient::generate_http_message(const Request& request) { |
| // Determine the method of the the request |
| std::stringstream message; |
| if (request.method == Request::HTTP_METHOD_DELETE) { |
| message << "DELETE"; |
| } else if (request.method == Request::HTTP_METHOD_GET) { |
| message << "GET"; |
| } else if (request.method == Request::HTTP_METHOD_POST) { |
| message << "POST"; |
| } |
| message << " /" << request.endpoint << " HTTP/1.1" << HTTP_EOL; |
| |
| // Generate the headers |
| bool is_post = request.method == Request::HTTP_METHOD_POST; |
| message << "Host: " << request.address << ":" << request.port << HTTP_EOL |
| << (is_post ? "Content-Type: application/json" HTTP_EOL : "") |
| << "Content-Length: " << ((is_post) ? request.content.size() : 0) << HTTP_EOL |
| << "Connection: close" << HTTP_EOL << HTTP_EOL << (is_post ? request.content : ""); |
| |
| // Return the HTTP message |
| TEST_LOG("[HTTP Message]: " << message.str()); |
| return message.str(); |
| } |