I'm developing a web service to serve json objects to a jeasyui async tree. My HTML has the following:
<ul id="tt" method="POST" class="easyui-tree" url="http://w.x.y.z:1024/testrest">
</ul>
Assume w.x.y.z is my server's IP address. According to the jeasyui documentation for their PHP json service, I need to return an array of dictionary objects that have keys id, text, and state. Okay, so far so good. I am attempting to develop a json service in c++ using the cpprest-sdk from Microsoft. I compiled and installed this library on RHEL 7.2 and am able to write some basic services using it. The problem lies (I think) with the encoding of the json that gets sent back to the client.
Here's a fully-functional example json server written with cpprest-sdk that handles POST requests and replies with a singly-populated array of dictionary objects that conform to the protocol expected by jeasyui:
#include <cpprest/http_listener.h>
#include <cpprest/json.h>
#pragma comment(lib, "cpprestlib" )
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;
#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;
#define TRACE(msg) wcout << msg
void handle_request(http_request request, function<void(const json::value &, json::value &, bool)> action)
{
json::value answer;
TRACE("\nHandle_request\n");
// Spit out the HTTP header to the console...
const auto HeaderString = request.to_string();
wcout << HeaderString.c_str() << endl;
request
.extract_json()
.then([&answer, &action](pplx::task<json::value> task) {
try
{
const auto & jvalue = task.get();
if (!jvalue.is_null())
{
action(jvalue, answer, false);
}
else
{
action(jvalue, answer, true);
}
}
catch (http_exception const & e)
{
wcout << "HTTP exception in handle_request: " << e.what() << endl;
}
})
.wait();
request.reply(status_codes::OK, answer);
}
void handle_post(http_request request)
{
TRACE("\nHandle POST\n");
handle_request(
request,
[](const json::value & jvalue, json::value & answer, bool bNull)
{
const utility::string_t sID("id");
const utility::string_t sText("text");
const utility::string_t sState("state");
if( bNull )
{
wcout << "jvalue must be null, setting some default values..." << endl;
json::value group;
group[sID] = json::value::string("1");
group[sText] = json::value::string("Hello");
group[sState] = json::value::string("closed");
answer[0] = group;
}
else
{
// To be written once the null case is sorted
}
}
);
}
int main()
{
uri_builder uri("http://w.x.y.z:1024/testrest");
http_listener listener(uri.to_uri());
listener.support(methods::POST, handle_post);
try
{
listener
.open()
.then([&listener]()
{
TRACE(L"\nStarting to listen\n");
})
.wait();
while (true);
}
catch (exception const & e)
{
wcout << e.what() << endl;
}
return 0;
}
This compiles cleanly and I can start the service on the linux server with the following:
./testrest &
Starting to listen
To aid in debugging, I've been using curl to serve as a POST client directly on the same linux server. I've been using the following command to send a POST request with 0 content-length:
curl -i -X POST -H 'Content-Type: application/json' http://w.x.y.z:1024/testrest
The output from curl is the following:
HTTP/1.1 200 OK
Content-Length: 44
Content-Type: application/json
[{"id":"1","state":"closed","text":"Hello"}]
and the console messages from my service are as such:
Handle POST
Handle_request
POST /testrest HTTP/1.1
Accept: */*
Content-Type: application/json
Host: w.x.y.z:1024
User-Agent: curl/7.29.0
jvalue must be null, setting some default values...
The first two lines correspond to the TRACE calls in the code. The middle section is generated by this section of code:
// Spit out the HTTP header to the console...
const auto HeaderString = request.to_string();
wcout << HeaderString.c_str() << endl;
Based on the curl output, which is an array of dictionary objects exactly one entry long, I would expect that this service should work just fine with the jeasyui javascript on the client. However, it does not. My async tree never populates and I don't see anything at all.
I suspect there's something wrong with the encoding, and so I wrote another service using web2py to test to see if it would work there. The following code exists in my default.py controller:
#service.json
def testweb2py():
aRet=[]
if request.post_vars.id is None:
mydict={'id':'1','text':'Hello','state':'closed'}
aRet.append(mydict)
return aRet
after modifying my client easyui-tree HTML to point to the web2py URL, it populates perfectly and I can see the node. I hit the web2py service.json code with curl just to see how the output might differ:
HTTP/1.1 200 OK
Date: Mon, 23 Jan 2017 18:17:17 GMT
Server: Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips mod_wsgi/3.4 Python/2.7.5
X-Powered-By: web2py
Expires: Mon, 23 Jan 2017 18:17:18 GMT
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Length: 99
Content-Type: application/json; charset=utf-8
[{"text": "Hello", "state": "closed", "id": "1"}]
Aside from the content header being quite different, there's one line that I suspect might have something to do with it:
Content-Type: application/json; charset=utf-8
In the call to the cpprest service, the header output from curl does not include charset=utf-8 in it. If I dump the curl output to a file using the -o switch, I don't see any clear difference between the encoding. The only thing that I can see different in the format of the json is some extra whitespace and the ordering:
[{"text": "Hello", "state": "closed", "id": "1"}] // web2py version
[{"id":"1","state":"closed","text":"Hello"}] // cpprest version
I'm unable to gain any control over the order in which the json dictionary is sent, but I doubt that has anything to do with it anyways. The extra whitespace prefixing the value entry seems irrelevant as well.
I've poured over the cpprest documentation over at microsoft.github.io/cpprestsdk/index.html,
and I cannot find anything that relates to setting the output encoding. There are a number of overrides to http_request::reply that include options for setting content-type, and I've gone down the road of calling them with hard-coded strings for both the json body and the content-type of json/application; charset=utf-8, all to no avail. I don't see how those overrides can be used with json::value objects at any rate, so I don't think that's the optimal path or a viable use of this cpprest library.
The jeasyui javascript code appears to be intentionally obfuscated, and I have little faith in being able to figure out what it is doing with the reply from the POST call. Maybe someone familiar with jeasyui can point to a viable means for debugging the async POST?
Please help!
So I figured out what was happening. Opened the developer tools console in Chrome and discovered the following error message:
XMLHttpRequest cannot load http://w.x.y.z:1024/testrest. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://w.x.y.z' is therefore not allowed access.
So it had nothing to do with the format or encoding of my json data, but rather the fact that the json service was identified as being a different resource from the web server that generated the client HTML, which it is, and so Chrome was blocking it. To solve this problem, I had to add some header fields in the response I sent back to the client, as well as add a support method to handle OPTIONS queries from any client that might need them.
In my main() function, I added:
listener.support(methods::OPTIONS, handle_options);
Then I wrote the corresponding function:
void handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("POST, OPTIONS"));
// Modify "Access-Control-Allow-Origin" header below to suit your security needs. * indicates allow all clients
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
request.reply(response);
}
Finally, I had to add the same headers to the request.reply in my handle_request:
http_response response(status_codes::OK);
// Without these headers, the client browser will likely refuse the data and eat it
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
response.set_body(answer);
request.reply(response);
There were additional problems, as well... the most prominent being the fact that the jeasyui class easyui_tree does not POST data with a Content-Type of application/json. Instead, it posts a Content-Type of application/x-www-form-urlencoded, so I had to add a function to parse the url encoding using libcurl. This also meant replacing request.extract_json() with request.extract_string() and related modifications to the corresponding lambda functions used by cpprest.
Here's the final example code, maybe it's useful to others working in these areas. It's a fully-functional example of writing a json service with cpprest (on linux, no less) that responds to asynchronous POST requests from easyui_tree. Dependencies: boost, cpprest, and libcurl-devel.
#include <boost/algorithm/string/replace.hpp>
#include <cpprest/http_listener.h>
#include <cpprest/json.h>
#include <curl/curl.h>
#pragma comment(lib, "cpprestlib" )
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;
#include <iostream>
#include <map>
#include <vector>
#include <set>
#include <string>
using namespace std;
#define TRACE(msg) wcout << msg
void build_json( const utility::string_t &source, json::value &jvalue )
{
// Use libcurl to unescape the POST body for us
vector<string> splitvec;
// We don't own the string created by curl_easy_unescape, so add a custom deleter
string text = shared_ptr<char>( curl_easy_unescape( 0, source.c_str(), 0, 0 ), curl_free).get();
// This works for this specific example of jeasyui, the class 'easyui-tree', which only passes id=... in the POST.
// Need custom handler to deal with more complicated data formats
boost::split( splitvec, text, boost::is_any_of("="));
if( splitvec.size() == 2 )
{
jvalue[splitvec.at(0)] = json::value::string(splitvec.at(1));
}
}
void handle_request(http_request request, function<void(const json::value &, json::value &, bool)> action)
{
json::value answer;
auto objHeader = request.headers();
auto sContentType = objHeader["Content-Type"];
// Two cases:
// 1) The very first call from easyui_tree, when the HTML is first loaded, will make a zero-length POST with no 'Content-Type' in the header
// 2) Subsequent calls from easyui_tree (e.g. when user opens a node) will have a Content-Type of 'application/x-www-form-urlencoded'
// Nowhere does easyui_tree send json data in the POST, although it expects json in the reply
if( sContentType.size() == 0 ||
!strncasecmp( sContentType.c_str(), "application/x-www-form-urlencoded", strlen("application/x-www-form-urlencoded") ) )
{
request
.extract_string()
.then([&answer, &action](pplx::task<utility::string_t> task) {
try
{
const auto & svalue = task.get();
json::value jvalue;
if ( svalue.size() == 0 )
{
action(jvalue, answer, true);
}
else
{
build_json( svalue, jvalue );
action(jvalue, answer, false);
}
}
catch (http_exception const & e)
{
wcout << "HTTP exception in handle_request: " << e.what() << endl;
}
})
.wait();
}
else
{
// This Content-Type doesn't appear with easyui_tree, but perhaps it's still useful for future cases...
if( !strncasecmp( sContentType.c_str(), "application/json", strlen("application/json") ) )
{
request
.extract_json()
.then([&answer, &action](pplx::task<json::value> task) {
try
{
const auto & jvalue = task.get();
if (!jvalue.is_null())
{
action(jvalue, answer, false);
}
else
{
action(jvalue, answer, true);
}
}
catch (http_exception const & e)
{
wcout << "HTTP exception in handle_request: " << e.what() << endl;
}
})
.wait();
}
}
http_response response(status_codes::OK);
// Without these headers, the client browser will likely refuse the data and eat it
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
response.set_body(answer);
request.reply(response);
}
void handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("POST, OPTIONS"));
// Modify "Access-Control-Allow-Origin" header below to suit your security needs. * indicates allow all clients
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("POST, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type"));
request.reply(response);
}
void handle_post(http_request request)
{
handle_request(
request,
[](const json::value & jvalue, json::value & answer, bool bInitialize)
{
if( bInitialize )
{
// First time the tree is being loaded, first id will be 16, which will yield us 16 child nodes when it POSTs back
json::value jreply;
jreply[U("id")] = json::value::string("16");
jreply[U("text")] = json::value::string("Parent");
jreply[U("state")] = json::value::string("closed");
answer[0] = jreply;
}
else
{
// User has opened a node
if( jvalue.type() == json::value::value_type::Object )
{
if( jvalue.has_field( "id" ) )
{
auto & key = jvalue.at( "id" );
if( key.is_string() )
{
auto value = key.as_string();
int id = atoi(value.c_str());
stringstream ss;
ss << (id / 2); // Each successive layer has half as many child nodes as the one prior
for( int i = 0; i < id; i++ )
{
json::value jreply;
jreply[U("id")] = json::value::string(ss.str());
jreply[U("text")] = json::value::string("Child");
jreply[U("state")] = json::value::string("closed");
answer[i] = jreply;
}
}
}
}
}
}
);
}
int main()
{
uri_builder uri("http://yourserver.com:1024/testrest");
http_listener listener(uri.to_uri());
listener.support(methods::POST, handle_post);
listener.support(methods::OPTIONS, handle_options);
try
{
listener
.open()
.then([&listener]()
{
TRACE(L"\nStarting to listen\n");
})
.wait();
while (true);
}
catch (exception const & e)
{
wcout << e.what() << endl;
}
return 0;
}
And then of course the corresponding HTML, assuming all the jeasyui scripts are referenced in the header:
<ul id="tt" method="POST" class="easyui-tree" url="http://yourserver.com:1024/testrest">
</ul>
Related
I have a Demo Server replying JSON objects only to the client request.
I am planning to use QNetworkAccessManager as the client, this is what I did.
I defined a lambda function handling Server reply
std::function<void(QNetworkReply*)> processReplyLB = [&](QNetworkReply *reply){
static int cnt = 0;
std::cout<<"--------------------"<<(++cnt)<<"---------------------"<<std::endl;
QList<QByteArray> headerList = reply->rawHeaderList();
foreach (QByteArray header, headerList) {
std::cout<<header.constData()<<" - "<<reply->rawHeader(header).constData()<<std::endl;
}
processResult = false;
if(reply->error()){
std::cout<<"REPLY ERROR"<<std::endl;
std::cout<<reply->errorString().toUtf8().constData()<<std::endl;
} else {
QString value = reply->readAll();
std::cout<<"value = "<<value.toUtf8().constData()<<std::endl;
QJsonDocument doc = QJsonDocument::fromJson(value.toUtf8());
if(doc.isNull()){
std::cout<<"JSON document is null"<<std::endl;
}else if(doc.isEmpty()){
std::cout<<"JSON document is empty"<<std::endl;
} else if(!doc.isObject()){
std::cout<<"JSON document is not an object"<<std::endl;
} else {
QJsonObject obj = doc.object();
QString responseStr = obj.value("result").toString();
processResult = (responseStr == "ok");
if(obj.contains("message")){
QJsonValue messageValue = obj.value("message");
std::cout<<messageValue.toString().toUtf8().constData()<<std::endl;
}
}
}
reply->deleteLater();
std::cout<<"--------------------"<<(cnt)<<"---------------------"<<std::endl;
};
and I connected this lambda slot to QNetworkAccessManager in two functions used for
check if client session does exist on Server(sends a get request).
login using id and password (send a post request with parameters).
in main function, if I invoke checkSession() or login() respectively, the result is fine. but if I try to call
login();
checkSession();
in sequence, then I will get lambda invoked four times with checkSession() result came as the first, following by a null json, login json result and finally another null json.
I know QNetworkAccessManager works asynchronously, EventLoop can solve this problem, but it is not applicable in real development mode due to I am writing a client background service component.
So how can we design this client so that I can make sure login result is processed before checkSession?
BTW, I used Java Servlet for Server without asynchronous. they are just trivial doGet and doPost processes.
I am trying to send post request with esp8266 programmed on arduino IDE, but there's lack of examples how to do it. I would like to send request to json server with raw input so it would look like this:
http://ip:port/something
BODY
{
"valuename":value
}
Would be grateful if anyone could show me such an example.
Greetings
For handling HTTP requests, you can use a RestClient library rather than writing all the low level requests. It saves a lot of time and is less error-prone.
For example, for a GET request, all you have to do is:
String response = "";
int statusCode = client.post("/", "foo=bar", &response);
One good such library with SSL support is written by github user DaKaz.
You can use it for your GET request. The returned response will be without the HTTP header. The function will return the response from the server without the headers.
Now you can use the ArduinoJson Library by bblanchin for decoding the JSON object.
Details can be seen here.
Or you can do plain string manipuation to get the values though it is not a recommended route to take and is prone to errors.
Here is an example to send JSON via HTTP library :
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ArduinoHttpClient.h>
#define JSON_BUF_SIZE 256
WiFiClient wifi;
HttpClient poster = HttpClient(wifi, IP, PORT);
void HTTPPost(){
String contentType = "application/json";
StaticJsonBuffer<JSON_BUF_SIZE> jsonBuffer;
JsonObject& jsonData = jsonBuffer.createObject();
jsonData["valuename"] = "value";
String postData = "";
jsonData.printTo(postData);
poster.post("/", contentType, postData);
printf("Trace : ResponseCode : %d\n", poster.responseStatusCode());
printf("Trace : Incoming Body : %s\n", poster.responseBody().c_str());
}
iam developing a rest service in c++ for a wordpress client in a project for a further education.
The service is written in c++ using casablanca as framework for and service and client communicate over JSON.
Now I have to send PDF Files to each other.
Can sb. tell me a method or an example to do this without sending direct links for a download?
http://casablanca.codeplex.com/
Here is my function to start the server and add the support methods.
void Commanagement::Init(utility::string_t url, utility::string_t port)
{
this->url = &url;
this->port = &port;
listener = new http_listener(U("http://localhost:4711"));
listener->support(methods::GET, std::bind(&Commanagement::handle_GET, this, std::placeholders::_1));
listener->support(methods::POST, std::bind(&Commanagement::handle_POST, this, std::placeholders::_1));
listener->open().wait();
}
And an example of sending a JSON response to my client.
void Commanagement::handle_POST(http_request message)
{
ucout << message.extract_json().wait();
auto paths = http::uri::split_path(http::uri::decode(message.relative_uri().path()));
json::value postData;
postData[L"id"] = json::value::number(13);
postData[L"FirstVal"] = json::value::string(L"Baseball");
postData[L"SomeVal"] = json::value::string(L"test");
message.reply(web::http::status_codes::OK, postData.serialize()).wait();
}
Files can be send serialized in the body to the clients.
This is an example only with the File Date, no headers and nothing else.
ifstream Stream;
Stream.open(FullFileName,std::ios::binary);
string Content, Line;
if (Stream)
{
while (getline(Stream,Line))
{
Content += Line;
}
}
Stream.close();
Request::request->set_body(Content);
I'm trying to send a POST request over an HTTPS-Connection via Qt5 to a Web-API, but I keep getting the following error-message:
Failure "Error downloading https://.../login - server replied: BAD REQUEST"
Reply:"{"error_tag": "ARGUMENT_MISSING", "error_code": 19, "error_extra": {"argument": "email"}, "error": "Required argument is missing"}"
It seems like the HTTPS Connection works, but the POST request is faulty...
void connection::sendLoginData(){
QUrl url = QString("https://.../login");
QNetworkRequest req(url);
//Creating the JSON-Data
QJsonDocument json;
QJsonObject data;
data["email"] = QString("a#g.com");
data["password"] = QString("---");
json.setObject(data);
QByteArray jsonPost = QJsonDocument(data).toJson();
req.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("application/json; charset=utf-8"));
req.setHeader(QNetworkRequest::ContentLengthHeader, QByteArray::number(jsonPost.size()));
//Sending the Request
QNetworkReply *reply = manager->post(req,jsonPost);
// Connection via HTTPS
QFile certFile(SSLCERTIFICATE);
certFile.open(QIODevice::ReadOnly);
QSslCertificate cert(&certFile, QSsl::Pem);
QSslSocket * sslSocket = new QSslSocket(this);
sslSocket->addCaCertificate(cert);
QSslConfiguration configuration = sslSocket->sslConfiguration();
configuration.setProtocol(QSsl::TlsV1_2);
sslSocket->setSslConfiguration(configuration);
reply->setSslConfiguration(configuration);
}
this is the Slot which is called when QNetworkReply gets a reply:
void connection::onFinished(QNetworkReply *reply){
if (reply->error() == QNetworkReply::NoError) {
//success
qDebug() << "Success" <<reply->readAll();
delete reply;
}
else {
//failure
qDebug() << "Failure" <<reply->errorString();
qDebug() << "Reply: " << reply->readAll();
delete reply;
}
}
The Signal "finished" of QNetworkReply is of course connected to the "onFinished"-Slot
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
manager = new QNetworkAccessManager;
ui->setupUi(this);
Connector = new connection(ui,manager);
connect(manager,SIGNAL(finished(QNetworkReply*)),Connector,SLOT(onFinished(QNetworkReply*)));
}
Maybe someone of you could tell me what's wrong with the POST-Request? Looking at the Reply of the Server it seems like the JSON-Data is never sent, or somehow formatted in a wrong way...
As you didn't provide the exact URL, I'd suggest you to try to check url.isValid() and url.errorString().
I just had an issue, the code worked fine in Qt 4.8, but in Qt 5.4 all my POST requests got 400 Bad Request reply.
I looked into the TCP dump via Wireshark and found out that the URL was wrong.
In my case url.setPath("api/register"); was the line that caused the problem. It should have been url.setPath("/api/register");
Hope it helps.
Recently I started taking this guide to get myself started on downloading files from the internet. I read it and came up with the following code to download the HTTP body of a website. The only problem is, it's not working. The code stops when calling the recv() call. It does not crash, it just keeps on running. Is this my fault? Am I using the wrong approch? I intent to use the code to not just download the contents of .html-files, but also to download other files (zip, png, jpg, dmg ...). I hope there's somebody that can help me. This is my code:
#include <stdio.h>
#include <sys/socket.h> /* SOCKET */
#include <netdb.h> /* struct addrinfo */
#include <stdlib.h> /* exit() */
#include <string.h> /* memset() */
#include <errno.h> /* errno */
#include <unistd.h> /* close() */
#include <arpa/inet.h> /* IP Conversion */
#include <stdarg.h> /* va_list */
#define SERVERNAME "developerief2.site11.com"
#define PROTOCOL "80"
#define MAXDATASIZE 1024*1024
void errorOut(int status, const char *format, ...);
void *get_in_addr(struct sockaddr *sa);
int main (int argc, const char * argv[]) {
int status;
// GET ADDRESS INFO
struct addrinfo *infos;
struct addrinfo hints;
// fill hints
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
// get address info
status = getaddrinfo(SERVERNAME,
PROTOCOL,
&hints,
&infos);
if(status != 0)
errorOut(-1, "Couldn't get addres information: %s\n", gai_strerror(status));
// MAKE SOCKET
int sockfd;
// loop, use first valid
struct addrinfo *p;
for(p = infos; p != NULL; p = p->ai_next) {
// CREATE SOCKET
sockfd = socket(p->ai_family,
p->ai_socktype,
p->ai_protocol);
if(sockfd == -1)
continue;
// TRY TO CONNECT
status = connect(sockfd,
p->ai_addr,
p->ai_addrlen);
if(status == -1) {
close(sockfd);
continue;
}
break;
}
if(p == NULL) {
fprintf(stderr, "Failed to connect\n");
return 1;
}
// LET USER KNOW
char printableIP[INET6_ADDRSTRLEN];
inet_ntop(p->ai_family,
get_in_addr((struct sockaddr *)p->ai_addr),
printableIP,
sizeof(printableIP));
printf("Connection to %s\n", printableIP);
// GET RID OF INFOS
freeaddrinfo(infos);
// RECEIVE DATA
ssize_t receivedBytes;
char buf[MAXDATASIZE];
printf("Start receiving\n");
receivedBytes = recv(sockfd,
buf,
MAXDATASIZE-1,
0);
printf("Received %d bytes\n", (int)receivedBytes);
if(receivedBytes == -1)
errorOut(1, "Error while receiving\n");
// null terminate
buf[receivedBytes] = '\0';
// PRINT
printf("Received Data:\n\n%s\n", buf);
// CLOSE
close(sockfd);
return 0;
}
void *get_in_addr(struct sockaddr *sa) {
// IP4
if(sa->sa_family == AF_INET)
return &(((struct sockaddr_in *) sa)->sin_addr);
return &(((struct sockaddr_in6 *) sa)->sin6_addr);
}
void errorOut(int status, const char *format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
exit(status);
}
If you want to grab files using HTTP, then libcURL is probably your best bet in C. However, if you are using this as a way to learn network programming, then you are going to have to learn a bit more about HTTP before you can retrieve a file.
What you are seeing in your current program is that you need to send an explicit request for the file before you can retrieve it. I would start by reading through RFC2616. Don't try to understand it all - it is a lot to read for this example. Read the first section to get an understanding of how HTTP works, then read sections 4, 5, and 6 to understand the basic message format.
Here is an example of what an HTTP request for the stackoverflow Questions page looks like:
GET http://stackoverflow.com/questions HTTP/1.1\r\n
Host: stackoverflow.com:80\r\n
Connection: close\r\n
Accept-Encoding: identity, *;q=0\r\n
\r\n
I believe that is a minimal request. I added the CRLFs explicitly to show that a blank line is used to terminate the request header block as described in RFC2616. If you leave out the Accept-Encoding header, then the result document will probably be transfered as a gzip-compressed stream since HTTP allows for this explicitly unless you tell the server that you do not want it.
The server response also contains HTTP headers for the meta-data describing the response. Here is an example of a response from the previous request:
HTTP/1.1 200 OK\r\n
Server: nginx\r\n
Date: Sun, 01 Aug 2010 13:54:56 GMT\r\n
Content-Type: text/html; charset=utf-8\r\n
Connection: close\r\n
Cache-Control: private\r\n
Content-Length: 49731\r\n
\r\n
\r\n
\r\n
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" ... 49,667 bytes follow
This simple example should give you an idea what you are getting into implementing if you want to grab files using HTTP. This is the best case, most simple example. This isn't something that I would undertake lightly, but it is probably the best way to learn and appreciate HTTP.
If you are looking for a simple way to learn network programming, this is a decent way to start. I would recommend picking up a copy of TCP/IP Illustrated, Volume 1 and UNIX Network Programming, Volume 1. These are probably the best way to really learn how to write network-based applications. I would probably start by writing an FTP client since FTP is a much simpler protocol to start with.
If you are trying to learn the details associated with HTTP, then:
Buy HTTP: the Definitive Guide and read it
Read RFC2616 until you understand it
Try examples using telnet server 80 and typing in requests by hand
Download the cURL client and use the --verbose and --include command line options so that you can see what is happening
Read Fielding's dissertation until HTTP really makes sense.
Just don't plan on writing your own HTTP client for enterprise use. You do not want to do that, trust me as one who has been maintaining such a mistake for a little while now...
The problem is, you have to implement the HTTP protocol. Downloading a file is not just a matter of connecting to the server, you have to send HTTP requests (along with proper HTTP header) before you get a response. After this, you would still need to parse the returned data to strip out more HTTP headers.
If you're just trying to download files using C, I suggest the cURL library, which does the HTTP work for you.
You have to send an HTTP request before expecting a response.
You code currently just waits for a response which never comes.
Also, don't write comments in all caps.