How to Make a Http Request With C++

Simple C example of doing an HTTP POST and consuming the response

A message has a header part and a message body separated by a blank line. The blank line is ALWAYS needed even if there is no message body. The header starts with a command and has additional lines of key value pairs separated by a colon and a space. If there is a message body, it can be anything you want it to be.

Lines in the header and the blank line at the end of the header must end with a carraige return and linefeed pair (see HTTP header line break style) so that's why those lines have \r\n at the end.

A URL has the form of http://host:port/path?query_string

There are two main ways of submitting a request to a website:

  • GET: The query string is optional but, if specified, must be reasonably short. Because of this the header could just be the GET command and nothing else. A sample message could be:

      GET /path?query_string HTTP/1.0\r\n
    \r\n
  • POST: What would normally be in the query string is in the body of the message instead. Because of this the header needs to include the Content-Type: and Content-Length: attributes as well as the POST command. A sample message could be:

      POST /path HTTP/1.0\r\n
    Content-Type: text/plain\r\n
    Content-Length: 12\r\n
    \r\n
    query_string

So, to answer your question: if the URL you are interested in POSTing to is http://api.somesite.com/apikey=ARG1&command=ARG2 then there is no body or query string and, consequently, no reason to POST because there is nothing to put in the body of the message and so nothing to put in the Content-Type: and Content-Length:

I guess you could POST if you really wanted to. In that case your message would look like:

POST /apikey=ARG1&command=ARG2 HTTP/1.0\r\n
\r\n

So to send the message the C program needs to:

  • create a socket
  • lookup the IP address
  • open the socket
  • send the request
  • wait for the response
  • close the socket

The send and receive calls won't necessarily send/receive ALL the data you give them - they will return the number of bytes actually sent/received. It is up to you to call them in a loop and send/receive the remainder of the message.

What I did not do in this sample is any sort of real error checking - when something fails I just exit the program. Let me know if it works for you:

#include <stdio.h> /* printf, sprintf */
#include <stdlib.h> /* exit */
#include <unistd.h> /* read, write, close */
#include <string.h> /* memcpy, memset */
#include <sys/socket.h> /* socket, connect */
#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */
#include <netdb.h> /* struct hostent, gethostbyname */

void error(const char *msg) { perror(msg); exit(0); }

int main(int argc,char *argv[])
{
/* first what are we going to send and where are we going to send it? */
int portno = 80;
char *host = "api.somesite.com";
char *message_fmt = "POST /apikey=%s&command=%s HTTP/1.0\r\n\r\n";

struct hostent *server;
struct sockaddr_in serv_addr;
int sockfd, bytes, sent, received, total;
char message[1024],response[4096];

if (argc < 3) { puts("Parameters: <apikey> <command>"); exit(0); }

/* fill in the parameters */
sprintf(message,message_fmt,argv[1],argv[2]);
printf("Request:\n%s\n",message);

/* create the socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) error("ERROR opening socket");

/* lookup the ip address */
server = gethostbyname(host);
if (server == NULL) error("ERROR, no such host");

/* fill in the structure */
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(portno);
memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length);

/* connect the socket */
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");

/* send the request */
total = strlen(message);
sent = 0;
do {
bytes = write(sockfd,message+sent,total-sent);
if (bytes < 0)
error("ERROR writing message to socket");
if (bytes == 0)
break;
sent+=bytes;
} while (sent < total);

/* receive the response */
memset(response,0,sizeof(response));
total = sizeof(response)-1;
received = 0;
do {
bytes = read(sockfd,response+received,total-received);
if (bytes < 0)
error("ERROR reading response from socket");
if (bytes == 0)
break;
received+=bytes;
} while (received < total);

/*
* if the number of received bytes is the total size of the
* array then we have run out of space to store the response
* and it hasn't all arrived yet - so that's a bad thing
*/
if (received == total)
error("ERROR storing complete response from socket");

/* close the socket */
close(sockfd);

/* process response */
printf("Response:\n%s\n",response);

return 0;
}

Like the other answer pointed out, 4096 bytes is not a very big response. I picked that number at random assuming that the response to your request would be short. If it can be big you have two choices:

  • read the Content-Length: header from the response and then dynamically allocate enough memory to hold the whole response.
  • write the response to a file as the pieces arrive

Additional information to answer the question asked in the comments:

What if you want to POST data in the body of the message? Then you do need to include the Content-Type: and Content-Length: headers. The Content-Length: is the actual length of everything after the blank line that separates the header from the body.

Here is a sample that takes the following command line arguments:

  • host
  • port
  • command (GET or POST)
  • path (not including the query data)
  • query data (put into the query string for GET and into the body for POST)
  • list of headers (Content-Length: is automatic if using POST)

So, for the original question you would run:

a.out api.somesite.com 80 GET "/apikey=ARG1&command=ARG2"

And for the question asked in the comments you would run:

a.out api.somesite.com 80 POST / "name=ARG1&value=ARG2" "Content-Type: application/x-www-form-urlencoded"

Here is the code:

#include <stdio.h> /* printf, sprintf */
#include <stdlib.h> /* exit, atoi, malloc, free */
#include <unistd.h> /* read, write, close */
#include <string.h> /* memcpy, memset */
#include <sys/socket.h> /* socket, connect */
#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */
#include <netdb.h> /* struct hostent, gethostbyname */

void error(const char *msg) { perror(msg); exit(0); }

int main(int argc,char *argv[])
{
int i;

/* first where are we going to send it? */
int portno = atoi(argv[2])>0?atoi(argv[2]):80;
char *host = strlen(argv[1])>0?argv[1]:"localhost";

struct hostent *server;
struct sockaddr_in serv_addr;
int sockfd, bytes, sent, received, total, message_size;
char *message, response[4096];

if (argc < 5) { puts("Parameters: <host> <port> <method> <path> [<data> [<headers>]]"); exit(0); }

/* How big is the message? */
message_size=0;
if(!strcmp(argv[3],"GET"))
{
message_size+=strlen("%s %s%s%s HTTP/1.0\r\n"); /* method */
message_size+=strlen(argv[3]); /* path */
message_size+=strlen(argv[4]); /* headers */
if(argc>5)
message_size+=strlen(argv[5]); /* query string */
for(i=6;i<argc;i++) /* headers */
message_size+=strlen(argv[i])+strlen("\r\n");
message_size+=strlen("\r\n"); /* blank line */
}
else
{
message_size+=strlen("%s %s HTTP/1.0\r\n");
message_size+=strlen(argv[3]); /* method */
message_size+=strlen(argv[4]); /* path */
for(i=6;i<argc;i++) /* headers */
message_size+=strlen(argv[i])+strlen("\r\n");
if(argc>5)
message_size+=strlen("Content-Length: %d\r\n")+10; /* content length */
message_size+=strlen("\r\n"); /* blank line */
if(argc>5)
message_size+=strlen(argv[5]); /* body */
}

/* allocate space for the message */
message=malloc(message_size);

/* fill in the parameters */
if(!strcmp(argv[3],"GET"))
{
if(argc>5)
sprintf(message,"%s %s%s%s HTTP/1.0\r\n",
strlen(argv[3])>0?argv[3]:"GET", /* method */
strlen(argv[4])>0?argv[4]:"/", /* path */
strlen(argv[5])>0?"?":"", /* ? */
strlen(argv[5])>0?argv[5]:""); /* query string */
else
sprintf(message,"%s %s HTTP/1.0\r\n",
strlen(argv[3])>0?argv[3]:"GET", /* method */
strlen(argv[4])>0?argv[4]:"/"); /* path */
for(i=6;i<argc;i++) /* headers */
{strcat(message,argv[i]);strcat(message,"\r\n");}
strcat(message,"\r\n"); /* blank line */
}
else
{
sprintf(message,"%s %s HTTP/1.0\r\n",
strlen(argv[3])>0?argv[3]:"POST", /* method */
strlen(argv[4])>0?argv[4]:"/"); /* path */
for(i=6;i<argc;i++) /* headers */
{strcat(message,argv[i]);strcat(message,"\r\n");}
if(argc>5)
sprintf(message+strlen(message),"Content-Length: %d\r\n",strlen(argv[5]));
strcat(message,"\r\n"); /* blank line */
if(argc>5)
strcat(message,argv[5]); /* body */
}

/* What are we going to send? */
printf("Request:\n%s\n",message);

/* create the socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) error("ERROR opening socket");

/* lookup the ip address */
server = gethostbyname(host);
if (server == NULL) error("ERROR, no such host");

/* fill in the structure */
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(portno);
memcpy(&serv_addr.sin_addr.s_addr,server->h_addr,server->h_length);

/* connect the socket */
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");

/* send the request */
total = strlen(message);
sent = 0;
do {
bytes = write(sockfd,message+sent,total-sent);
if (bytes < 0)
error("ERROR writing message to socket");
if (bytes == 0)
break;
sent+=bytes;
} while (sent < total);

/* receive the response */
memset(response,0,sizeof(response));
total = sizeof(response)-1;
received = 0;
do {
bytes = read(sockfd,response+received,total-received);
if (bytes < 0)
error("ERROR reading response from socket");
if (bytes == 0)
break;
received+=bytes;
} while (received < total);

/*
* if the number of received bytes is the total size of the
* array then we have run out of space to store the response
* and it hasn't all arrived yet - so that's a bad thing
*/
if (received == total)
error("ERROR storing complete response from socket");

/* close the socket */
close(sockfd);

/* process response */
printf("Response:\n%s\n",response);

free(message);
return 0;
}

How do you make a HTTP request with C++?

I had the same problem. libcurl is really complete. There is a C++ wrapper curlpp that might interest you as you ask for a C++ library. neon is another interesting C library that also support WebDAV.

curlpp seems natural if you use C++. There are many examples provided in the source distribution.
To get the content of an URL you do something like that (extracted from examples) :

// Edit : rewritten for cURLpp 0.7.3
// Note : namespace changed, was cURLpp in 0.7.2 ...

#include <curlpp/cURLpp.hpp>
#include <curlpp/Options.hpp>

// RAII cleanup

curlpp::Cleanup myCleanup;

// Send request and get a result.
// Here I use a shortcut to get it in a string stream ...

std::ostringstream os;
os << curlpp::options::Url(std::string("http://example.com"));

string asAskedInQuestion = os.str();

See the examples directory in curlpp source distribution, there is a lot of more complex cases, as well as a simple complete minimal one using curlpp.

my 2 cents ...

How to make an HTTP get request in C without libcurl?

Using BSD sockets or, if you're somewhat limited, say you have some RTOS, some simpler TCP stack, like lwIP, you can form the GET/POST request.

There are a number of open-source implementations. See the "happyhttp" as a sample ( http://scumways.com/happyhttp/happyhttp.html ). I know, it is C++, not C, but the only thing that is "C++-dependant" there is a string/array management, so it is easily ported to pure C.

Beware, there are no "packets", since HTTP is usually transfered over the TCP connection, so technically there is only a stream of symbols in RFC format. Since http requests are usually done in a connect-send-disconnect manner, one might actually call this a "packet".

Basically, once you have an open socket (sockfd) "all" you have to do is something like

char sendline[MAXLINE + 1], recvline[MAXLINE + 1];
char* ptr;

size_t n;

/// Form request
snprintf(sendline, MAXSUB,
"GET %s HTTP/1.0\r\n" // POST or GET, both tested and works. Both HTTP 1.0 HTTP 1.1 works, but sometimes
"Host: %s\r\n" // but sometimes HTTP 1.0 works better in localhost type
"Content-type: application/x-www-form-urlencoded\r\n"
"Content-length: %d\r\n\r\n"
"%s\r\n", page, host, (unsigned int)strlen(poststr), poststr);

/// Write the request
if (write(sockfd, sendline, strlen(sendline))>= 0)
{
/// Read the response
while ((n = read(sockfd, recvline, MAXLINE)) > 0)
{
recvline[n] = '\0';

if(fputs(recvline, stdout) == EOF)
{
printf("fputs() error\n");
}

/// Remove the trailing chars
ptr = strstr(recvline, "\r\n\r\n");

// check len for OutResponse here ?
snprintf(OutResponse, MAXRESPONSE,"%s", ptr);
}
}

HTTP Request using Sockets in C

The reason the server is timing out is because you are not sending a valid request. Like Halim pointed out, your request data incomplete, you are using LF instead of CRLF for the line breaks, and missing the final line break to end the request header.

But, even after fixing that, your code is STILL not sending the request correctly. This is because you are using a char* to point at your request data, and then passing sizeof(char*) as the data length to send(). So you are only sending 4 bytes ("GET ") or 8 bytes ("GET /ind"), depending on whether you are compiling a 32bit or 64bit executable. You need to use strlen() instead of sizeof():

char *header = "GET /index.html HTTP/1.1\r\nHost: www.example.com\r\n\r\n";
send(sockfd,header,strlen(header),0);

Once you get that part working, your recv() logic is not parsing the server's response at all, which I assume is you simply not having gotten that far yet. But more importantly, the data being received is not null-terminated, but your call to printf() after recv() assumes that it is. You need to fix that as well, either like this:

byte_count = recv(sockfd,buf,sizeof(buf)-1,0); // <-- -1 to leave room for a null terminator
buf[byte_count] = 0; // <-- add the null terminator
printf("recv()'d %d bytes of data in buf\n",byte_count);
printf("%s",buf);

Or, like this:

byte_count = recv(sockfd,buf,sizeof(buf),0);
printf("recv()'d %d bytes of data in buf\n",byte_count);
printf("%.*s",byte_count,buf); // <-- give printf() the actual data size

And, of course, none of your code has any error handling in it at all. You really need to do that.

simple HTTP request and response in C language

You will need an external library to solve your problem and libcurl is an excellent alternative.

Give a try in this piece of (tested) code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <curl/curl.h>


typedef struct string_buffer_s
{
char * ptr;
size_t len;
} string_buffer_t;


static void string_buffer_initialize( string_buffer_t * sb )
{
sb->len = 0;
sb->ptr = malloc(sb->len+1);
sb->ptr[0] = '\0';
}


static void string_buffer_finish( string_buffer_t * sb )
{
free(sb->ptr);
sb->len = 0;
sb->ptr = NULL;
}


static size_t string_buffer_callback( void * buf, size_t size, size_t nmemb, void * data )
{
string_buffer_t * sb = data;
size_t new_len = sb->len + size * nmemb;

sb->ptr = realloc( sb->ptr, new_len + 1 );

memcpy( sb->ptr + sb->len, buf, size * nmemb );

sb->ptr[ new_len ] = '\0';
sb->len = new_len;

return size * nmemb;

}


static size_t header_callback(char * buf, size_t size, size_t nmemb, void * data )
{
return string_buffer_callback( buf, size, nmemb, data );
}


static size_t write_callback( void * buf, size_t size, size_t nmemb, void * data )
{
return string_buffer_callback( buf, size, nmemb, data );
}


int main( int argc, char * argv[] )
{
CURL * curl;
CURLcode res;
string_buffer_t strbuf;

char * myurl = argv[1];

string_buffer_initialize( &strbuf );

curl = curl_easy_init();

if(!curl)
{
fprintf(stderr, "Fatal: curl_easy_init() error.\n");
string_buffer_finish( &strbuf );
return EXIT_FAILURE;
}

curl_easy_setopt(curl, CURLOPT_URL, myurl );
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L );
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback );
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback );
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &strbuf );
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &strbuf );

res = curl_easy_perform(curl);

if( res != CURLE_OK )
{
fprintf( stderr, "Request failed: curl_easy_perform(): %s\n", curl_easy_strerror(res) );

curl_easy_cleanup( curl );
string_buffer_finish( &strbuf );

return EXIT_FAILURE;
}

printf( "%s\n\n", strbuf.ptr );

curl_easy_cleanup( curl );
string_buffer_finish( &strbuf );

return EXIT_SUCCESS;
}

/* eof */

If libcurl is not installed yet (curl.h cannot be found):

$ sudo apt-get install libcurl4-openssl-dev

Compiling (gcc/linux):

$ gcc -Wall httpget.c -lcurl -o httpget

Testing:

$ ./httpget "http://www.google.com/search?q=mkyong"

Making a Get HTTP request in C

Reason for warning: You are writing more bytes (4 bytes extra) to host_server->h_addr, which causes overflow. You can only write host_server->h_length bytes.

Solution:

change

bcopy((char*) host_server->h_addr, (char*) &server_addr.sin_addr.s_addr, sizeof(server_addr));

to

bcopy((char*) host_server->h_addr, (char*) &server_addr.sin_addr.s_addr, host_server->h_length);

`

How to make a C program issue an HTTP request and read the response?

The very simplest way would be to use a system() call with curl. For example:

#include <stdlib.h>

int main() {
system("curl http://example.com/");
}

This will send a GET request to example.com and print the output. Depending on your application, that might be enough. (See the manpage for more options.)

However, actually capturing the output and doing error handling will be more difficult than if you'd used a C library like curl.

Here's an example of how to use curl-the-library: https://curl.haxx.se/libcurl/c/simple.html



Related Topics



Leave a reply



Submit