/**
\file WinHttpHandler.cpp
Copyright Notice\n
Copyright (C) 2017 Jan Rogall - developer\n
Copyright (C) 2017 Moritz Wirger - developer\n
This file is part of hueplusplus.
hueplusplus is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
hueplusplus is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with hueplusplus. If not, see .
**/
#include "hueplusplus/WinHttpHandler.h"
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "Ws2_32.lib")
namespace hueplusplus
{
namespace
{
class AddrInfoFreer
{
public:
explicit AddrInfoFreer(addrinfo* p) : p(p) {}
~AddrInfoFreer() { freeaddrinfo(p); }
private:
addrinfo* p;
};
class SocketCloser
{
public:
explicit SocketCloser(SOCKET s) : s(s) {}
~SocketCloser() { closesocket(s); }
private:
SOCKET s;
};
} // namespace
WinHttpHandler::WinHttpHandler()
{
// Initialize Winsock
int return_code = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (return_code != 0)
{
std::cerr << "WinHttpHandler: Failed to open socket: " << return_code << std::endl;
throw(std::system_error(return_code, std::system_category(), "WinHttpHandler: Failed to open socket"));
}
}
WinHttpHandler::~WinHttpHandler()
{
WSACleanup();
}
std::string WinHttpHandler::send(const std::string& msg, const std::string& adr, int port) const
{
struct addrinfo hints = {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
struct addrinfo* result = nullptr;
if (getaddrinfo(adr.c_str(), std::to_string(port).c_str(), &hints, &result) != 0)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: getaddrinfo failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: getaddrinfo failed"));
}
SOCKET connect_socket = INVALID_SOCKET;
int connectError = 0;
{
AddrInfoFreer freeResult(result);
// Attempt to connect to the first address returned by
// the call to getaddrinfo
struct addrinfo* ptr = result;
// Create a SOCKET for connecting to server
connect_socket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (connect_socket == INVALID_SOCKET)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: Error at socket(): " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: Error at socket()"));
}
// Connect to server.
if (connect(connect_socket, ptr->ai_addr, (int)ptr->ai_addrlen) == SOCKET_ERROR)
{
connectError = WSAGetLastError();
closesocket(connect_socket);
connect_socket = INVALID_SOCKET;
}
// Should really try the next address returned by getaddrinfo
// if the connect call failed
// But for this simple example we just free the resources
// returned by getaddrinfo and print an error message
}
if (connect_socket == INVALID_SOCKET)
{
std::cerr << "WinHttpHandler: Unable to connect to server!" << std::endl;
throw std::system_error(connectError, std::system_category(), "WinHttpHandler: Unable to connect to server!");
}
SocketCloser closeSocket(connect_socket);
// Send an initial buffer
if (::send(connect_socket, msg.c_str(), msg.size(), 0) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: send failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: send failed"));
}
// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
if (shutdown(connect_socket, SD_SEND) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: shutdown failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: shutdown failed"));
}
const int recvbuflen = 128;
char recvbuf[recvbuflen];
// Receive data until the server closes the connection
std::string response;
int res;
do
{
res = recv(connect_socket, recvbuf, recvbuflen, 0);
if (res > 0)
{
// std::cout << "WinHttpHandler: Bytes received: " << res << std::endl;
response.append(recvbuf, res);
}
else if (res == 0)
{
// std::cout << "WinHttpHandler: Connection closed " << std::endl;
}
else
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: recv failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: recv failed"));
}
} while (res > 0);
return response;
}
std::vector WinHttpHandler::sendMulticast(
const std::string& msg, const std::string& adr, int port, std::chrono::steady_clock::duration timeout) const
{
struct addrinfo hints = {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
struct addrinfo* result = nullptr;
if (getaddrinfo(adr.c_str(), std::to_string(port).c_str(), &hints, &result) != 0)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: getaddrinfo failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: getaddrinfo failed"));
}
AddrInfoFreer freeResult(result);
// Attempt to connect to the first address returned by
// the call to getaddrinfo
struct addrinfo* ptr = result;
// Create a SOCKET for connecting to server
SOCKET connect_socket = socket(ptr->ai_family, ptr->ai_socktype, 0);
if (connect_socket == INVALID_SOCKET)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: Error at socket(): " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: Error at socket()"));
}
SocketCloser closeSocket(connect_socket);
// Fill out source socket's address information.
SOCKADDR_IN source_sin;
source_sin.sin_family = AF_INET;
source_sin.sin_port = htons(0);
source_sin.sin_addr.s_addr = htonl(INADDR_ANY);
// Associate the source socket's address with the socket, Sock.
if (bind(connect_socket, (struct sockaddr FAR*)&source_sin, sizeof(source_sin)) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: Binding socket failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: Binding socket failed"));
}
u_long sock_mode = 1;
ioctlsocket(connect_socket, FIONBIO, &sock_mode);
BOOL bOptVal = TRUE;
setsockopt(connect_socket, SOL_SOCKET, SO_BROADCAST, (char*)&bOptVal, sizeof(bOptVal));
// Set the Time-to-Live of the multicast.
int iOptVal = 1; // for same subnet, but might be increased to 16
if (setsockopt(connect_socket, IPPROTO_IP, IP_MULTICAST_TTL, (char FAR*)&iOptVal, sizeof(int)) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: setsockopt failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: setsockopt failed"));
}
// Fill out the desination socket's address information.
SOCKADDR_IN dest_sin;
dest_sin.sin_family = AF_INET;
dest_sin.sin_port = htons(port);
dest_sin.sin_addr.s_addr = inet_addr((const char*)ptr->ai_addr);
// Send a message to the multicasting address.
if (sendto(connect_socket, msg.c_str(), msg.size(), 0, (struct sockaddr FAR*)&dest_sin, sizeof(dest_sin))
== SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: sendto failed: " << WSAGetLastError() << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: sendto failed"));
}
// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
if (shutdown(connect_socket, SD_SEND) == SOCKET_ERROR)
{
int err = WSAGetLastError();
std::cerr << "WinHttpHandler: sendMulticast: shutdown failed: " << err << std::endl;
throw(std::system_error(err, std::system_category(), "WinHttpHandler: sendMulticast: shutdown failed"));
}
std::string response;
const int recvbuflen = 2048;
char recvbuf[recvbuflen] = {};
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start < timeout)
{
int res = recv(connect_socket, recvbuf, recvbuflen, 0);
if (res > 0)
{
// std::cout << "WinHttpHandler: sendMulticast: Bytes received: " << res
// << std::endl;
response.append(recvbuf, res);
}
else if (res == 0)
{
// std::cout << "WinHttpHandler: sendMulticast: Connection closed " <<
// std::endl;
}
else
{
// No exception here due to non blocking socket
// std::cerr << "sendMulticast: recv failed: " << WSAGetLastError() <<
// std::endl; throw(std::runtime_error("recv failed"));
}
}
// construct return vector
std::vector returnString;
size_t pos = response.find("\r\n\r\n");
size_t prevpos = 0;
while (pos != std::string::npos)
{
returnString.push_back(response.substr(prevpos, pos - prevpos));
pos += 4;
prevpos = pos;
pos = response.find("\r\n\r\n", pos);
}
return returnString;
}
} // namespace hueplusplus