Troliver

stories of war between boy and machine

Uploading data to a webserver Part 2 – C/C++ and sockets alternative

Back in Part 1, I demonstrated an example of uploading data to a webserver with cURL. The thing that bugged me was, however, that this used external libraries which can be annoying to deploy with. One option is to statically link the cURL libraries, which will make it huge, but I decided to go native in Windows and give Winsock a go.

Working from the example in Part 1, one of the first things I decided to do was to remove any reference to cURL. Actually, the only place you need to really do this in the previous example would be in the function where you actually upload the string (and, of course, any library or header reference). With that out of the way, we now need to make a socket (which, on Windows, you should only need to use winsock.h and Ws2_32.lib to achieve).

WSADATA wsaData; 
if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
{
	logfileOutput("WSAStartup failed.");
	exit(1);
}

This first snippet is needed to initialise Winsock (you might want to also add a way to capture the exit code, too). Once that’s done, I’ve gone and replaced the old cURL code with some Winsock code:

struct addrinfo hints;
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;          // IPv4
	hints.ai_protocol = IPPROTO_TCP;    // TCP
	hints.ai_socktype = SOCK_STREAM;    

	struct addrinfo* targetAdressInfo = NULL;
	DWORD getAddrRes = getaddrinfo(address, NULL, &hints, &targetAdressInfo);

	if (getAddrRes != 0 || targetAdressInfo == NULL)
	{
		logfileOutput("Could not resolve the hostname.");
	}

	SOCKADDR_IN sockAddr;
	sockAddr.sin_addr = ((struct sockaddr_in*) targetAdressInfo->ai_addr)->sin_addr;    
	sockAddr.sin_family = AF_INET;  // IPv4
	sockAddr.sin_port = htons(80);  // HTTP Port: 80
								
	freeaddrinfo(targetAdressInfo);

	SOCKET webSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (webSocket == INVALID_SOCKET)
	{
		logfileOutput("Creation of the socket failed!");
	}

	printf("\nConnecting... ");
	if (connect(webSocket, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) != 0)
	{
		logfileOutput("Could not connect");
		closesocket(webSocket);
	}
	printf("Connected");


	// Here's where we send the data
	const char* httpRequest = postdata;
	int sentBytes = send(webSocket, httpRequest, strlen(httpRequest), 0);
	if (sentBytes < strlen(httpRequest) || sentBytes == SOCKET_ERROR)
	{
		logfileOutput("Could not send the request to the server");
		closesocket(webSocket);
	}

 

Because it is an incredibly quick and dirty example, I won’t explain it too much. I would actually prefer using something like cURL, which has already been done, but the basic idea is that you set up a socket, connect to the webserver and then send the data. Notice that there is no provision for receiving or interpreting responses, so this is really unhelpful for debugging. But the point is to illustrate that you can send data using what is already provided by Microsoft in the Winsock library.

The two piece of data – httprequest (and data) and address – represent the webserver URL (e.g. http://10.0.0.1/) and the request data itself. This data is not simply the string that we had previously; we need to make another string that wraps around the string of values to send. To do that, I’ve made the following code, which creates a string in the format that is expected for the data to be received in:

	sprintf_s(postdata,
		"POST %s HTTP/1.1\r\n"
		"Host: %s\r\n"
		"Content-Type: application/x-www-form-urlencoded\r\n"
		"Content-Length: %i\r\n\r\n"
		"%s\r\n", settingsPage, settingsServer, strlen(requestString), requestString);

To break it down a little:

  • The first part of this, POST %s, tells the page defined by settingsPage that this is POST data. The page should be the one that you created earlier; so this could be senddata.php or something.
  • It is important for the content-type field to report that it is application/x-www-form-urlencoded and not text/html as I originally had tried to do. It just won’t send the data in the body as you want it to, otherwise!
  • Content-length has to be correct – but that is simply the length of the requestString.
  • Finally, after two carriage returns (and I think that this has to be the case), you include the data you want to include (in other words, the payload of requestString) that was generated in the previous example.

You can test what it comes out as with a simple printf of postdata, which should show you what the server will see. This is then sent to the server in the socket code above.

Again, this is a very quick and dirty way to get a socket to send the same data as in the last example and is really just to show that you can do it. There is very little error catching, format checking and it doesn’t capture a response. But with a bit of work, it can eliminate a lot of un-necessary code that more cumbersome (cURL) libraries might include if all you’re looking to do is to create the bare minimum for a small footprint binary executable.

, , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.