I have a problem when using CURL to get MJPEG Stream from IP Camera Panasonic.
Here is my code.
int _tmain(int argc, _TCHAR* argv[])
{
CURL *curl;
CURLcode res;
/* Minimalistic http request */
char request[1000];
strcpy(request, "GET /nphMotionJpeg?Resolution=640x480&Quality=Standard HTTP/1.1\r\n\r\n") ;
curl_socket_t sockfd; /* socket */
long sockextr;
size_t iolen;
curl = curl_easy_init();
if(curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "http://192.168.1.253");
curl_easy_setopt(curl, CURLOPT_USERPWD, "my_usr:my_pass");
/* Do not do the transfer - only connect to host */
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L);
res = curl_easy_perform(curl);
if(CURLE_OK != res)
{
printf("Error: %s\n", strerror(res));
return 1;
}
res = curl_easy_getinfo(curl, CURLINFO_LASTSOCKET, &sockextr);
if(CURLE_OK != res)
{
printf("Error: %s\n", curl_easy_strerror(res));
return 1;
}
sockfd = sockextr;
/* wait for the socket to become ready for sending */
if(wait_on_socket(sockfd, false, 6000L) < 0)
{
printf("Error: timeout.\n");
return 1;
}
iolen = 0;
res = curl_easy_send(curl, request, strlen(request), &iolen);
if(CURLE_OK != res)
{
printf("Error: %s\n", curl_easy_strerror(res));
return 1;
}
//puts("Reading response.");
/* read the response */
for(;;)
{
char* buf = new char[1024*100];
iolen = 0;
wait_on_socket(sockfd, true, 60000L);
res = curl_easy_recv(curl, buf, 1024*100, &iolen);
if(CURLE_OK != res)
break;
fstream f;
f.open("out.txt", ios::out|ios::binary|ios::app);
if(!f.fail())
{
f.write(buf,iolen);
f.close();
}
delete []buf;
}
/* always cleanup */
curl_easy_cleanup(curl);
}
return 0;
}
The result is content in buf after the first loop as description of Panasonic document.
But it only have 3 frames jpeg in data responding. and total size only is 3056 bytes. That mean is about 1KB/Jpeg image.It is wrong.
And in the second loop, the curl_easy_recv() always return CURLE_UNSUPPORTED_PROTOCOL.
I also change the request string same as description of Panasonic document:
"GET http://xxx.xxx.xxx.xxx:yy/nphMotionJpeg?Resolution=640x480&Quality=Standard HTTP/1.0\r\n"
OR
"GET http://usr:pass#xxx.xxx.xxx.xxx:yy/nphMotionJpeg?Resolution=640x480&Quality=Standard HTTP/1.0\r\n"
--> it will response "BAD REQUEST".
The model of my camera is Panasonic BL-C111CE.
Here's a much better example source code you could start from:
http://curl.haxx.se/libcurl/c/url2file.html
You really SHOULD avoid CURLOPT_CONNECT_ONLY, curl_easy_recv() and curl_easy_send() unless you know PERFECTLY well what you're doing and why the "normal" API isn't good enough. More often than otherwise they are the wrong answer to your problems. libcurl does HTTP perfectly fine on its own already!
Nitpick: at one point you call strerror() on a return code from libcurl, which won't show the correct error string...
I have found out the problem.
The reason with command "GET /nphMotionJpeg?Resolution=640x480&Quality=Standard HTTP/1.1\r\n\r\n" is not permitted to access video.
I solve it by changing the setting in my camera that can permit Guest user access to video.
Thanks all!
Related
I'm implementing a function that should upload a file. I'm using cUrl and Qt/C++.
int FtpController::uploadFile()
{
#define UPLOAD_FILE_AS "temp.txt"
#define RENAME_FILE_TO "renamed-and-fine.txt"
CURL* curl;
curl = curl_easy_init();
CURLcode result;
FILE* hd_src;
const auto str = getFileName().toStdString(); // getFileName() provides the full path
const auto* const localFile = str.c_str();
struct stat file_info;
unsigned long fsize;
struct curl_slist* headerlist = nullptr;
static const char buf_1[] = "RNFR " UPLOAD_FILE_AS;
static const char buf_2[] = "RNTO " RENAME_FILE_TO;
/* get the file size of the local file */
if (stat(localFile, &file_info)) {
printf("Couldn't open '%s': %s\n", localFile, strerror(errno));
return 1;
}
fsize = (unsigned long)file_info.st_size;
printf("Local file size: %lu bytes.\n", fsize);
/* get a FILE * of the same file */
hd_src = fopen(localFile, "rb");
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if (curl) {
/* build a list of commands to pass to libcurl */
headerlist = curl_slist_append(headerlist, buf_1);
headerlist = curl_slist_append(headerlist, buf_2);
{
curl_easy_setopt(curl, CURLOPT_PORT, _params.port);
curl_easy_setopt(curl, CURLOPT_USERNAME, _params.username.toStdString().c_str());
curl_easy_setopt(curl, CURLOPT_PASSWORD, _settingsController->getSystemSettings()->getPass().toStdString().c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT, _params.timeout);
curl_easy_setopt(curl, CURLOPT_URL, _params.url.toString().toStdString().c_str());
/* If you intend to use this on windows with a libcurl DLL, you must use CURLOPT_WRITEFUNCTION as well */
}
/* Now run off and do what you have been told! */
result = curl_easy_perform(curl);
/* Check for errors */
if (result != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(result));
/* clean up the FTP commands list */
curl_slist_free_all(headerlist);
/* always cleanup */
curl_easy_cleanup(curl);
}
fclose(hd_src); /* close the local file */
curl_global_cleanup();
return 0;
}
It returns: curl_easy_perform() failed: Access denied to remote resource
However, my web-hosting returns:
What's wrong? I triple checked all input.
The file is definitely found, the curl writes in the terminal Local file size: 575131 bytes., though I don't ask it.
If I change the pass, it returns curl_easy_perform() failed: Login denied and if the URL is different or I try uploading anything it says curl_easy_perform() failed: URL using bad/illegal format or missing URL
This is the third approach to sending files via FTP, I need a help.
How do you add a prefix for the output of command execution with c++
localhost is a Flask web application
std::string exec(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&_pclose)> pipe(_popen(cmd, "r"), _pclose);
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
//std::cout << typeid(result).name() << std::endl;
// read form pipe and add to the output string
std::string output = "output=";
output += buffer.data()
std::cout << output << std::endl;
// call report_ to send a post request to the server
report_(output);
}
char* c = const_cast<char*>(result.c_str());
return result;
}
As far as I understand this is a c++ function that returns a string value of the output from the command prompt
int report_(std::string report )
{
CURL* curl;
CURLcode res;
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if (curl) {
/* First set the URL that is about to receive our POST. This URL can
just as well be a https:// URL if that is what should receive the
data. */
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost/api/00000000000000000000/report");
/* Now specify the POST data */
// report starts with "output="
std::cout << report << std::endl;
// this is where we add the post data
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, output );
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if (res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}
This function reports the output of the exec() function but before you do that you have to add the prefix output= to the output of exec() which takes a string as an argument
The server returns
400 Bad Request: The browser (or proxy) sent a request that this server could not understand. KeyError: 'output'
If you change curl_easy_setopt(curl, CURLOPT_POSTFIELDS, output ); to curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "output=hello world" ); then the server receives the output
This link explains how to add post data to post fields you have to pass a pointer to the data you want to send so using const char*
// this line refers to the pointer of the string needed to be send over
// just replace output with and std::string value and you can send it as post data
// do not use std::string as post data
const char* c = const_cast<char*>(output.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, c );
I have started working with libcurl, and simply tried running the basic code to get a file from an url. When I get this file using curl.exe, compiled with the same library, I get detect no random traffic on my localhost. However, when I run with my own executable, I get around 19 packets sent between two localhost ports.
I make sure to call curl_global_init(CURL_GLOBAL_WIN32) and curl_global_cleanup() after the method call.
What could be the cause of this traffic, and how could I make it go away?
int CurlFileDownloader::downloadSingleFile(const std::string& url, const std::string& destination) {
CURLcode res = CURLE_READ_ERROR;
mHandle = curl_easy_init();
if(mHandle) {
mData.destinationFolder = destination;
// Get the file name from the url
auto lastPos = url.find_last_of("/");
mData.fileName = url.substr(lastPos + 1);
curl_easy_setopt(mHandle, CURLOPT_URL, url.c_str());
/* Define our callback to get called when there's data to be written */
curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, &CurlFileDownloader::writeFileContent);
/* Set a pointer to our struct to pass to the callback */
curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, &mData);
/* Switch on full protocol/debug output */
curl_easy_setopt(mHandle, CURLOPT_VERBOSE, 1L);
mLastError = curl_easy_perform(mHandle);
/* always cleanup */
curl_easy_cleanup(mHandle);
if (mData.fileStream.is_open()) {
mData.fileStream.close();
}
if(CURLE_OK != mLastError) {
std::cerr << "Curl error " << mLastError << std::endl;
}
}
return mLastError;
}
size_t CurlFileDownloader::writeFileContent(char *buffer, size_t size, size_t nmemb, void *cb_data) {
struct CurlCallbackData *data = (CurlCallbackData*)cb_data;
size_t written = 0;
if (data->fileStream.is_open()) {
data->fileStream.write(buffer, nmemb);
}
else {
/* listing output */
if (data->destinationFolder != "") {
data->fileStream.open(data->destinationFolder + "\\" + data->fileName, std::ios::out | std::ios::binary);
}
else {
data->fileStream.open(data->fileName, std::ios::out | std::ios::binary);
}
data->fileStream.write(buffer, nmemb);
}
return nmemb;
}
Here is a sample of what RawCap.exe is capturing.
The source of the localhost communication was the use of a socket pair for IPV4 Loopback. When removing the #USE_SOCKETPAIR from libCURL's socketpair.h, the issue went away.
I want to retry a curl connection in my C++ program for 5 times. When it fails 5 times in a row, it should stop the execution of the program. However, it stops after the first error at this point. I am able to catch the error, however I don't know how to execute the previous curl connection. E.g., with jQuery I can use something like $.ajax(this);. For LibCurl in C++ I am looking for a similar solution.
My current LibCurl code is shown below, note that I use multiple curl connections which all have other settings, therefore I would like a general approach which I can use for all my LibCurl errors within my LibcurlError function which is also included below.
curl = curl_easy_init();
if (curl) {
CurlResponse = "";
host = "http://google.com";
LibcurlHeaders = curl_slist_append(NULL, "Expect:");
if (ProxyAddress.length() > 0) {
curl_easy_setopt(curl, CURLOPT_PROXY, ProxyAddress.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, (host).c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER , 1);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST , 1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, LibcurlHeaders);
res = curl_easy_perform(curl);
curl_slist_free_all(LibcurlHeaders);
if (res != CURLE_OK) {
//AT THIS POINT I WOULD LIKE TO RETRY FOR 5 TIMES WHICH I WOULD LIKE TO CATCH IN MY LibcurlError FUNCTION.
LibcurlError(curl_easy_strerror(res), host);
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
void LibcurlError(string error, string host) {
//IF FAILED FOR LESS THEN 5 TIMES IN A ROW -> RETRY CURL
//ELSE I WOULD LIKE TO EXECUTE MY ORIGINAL CODE WHICH IS STATED BELOW
Message = "LibCurl Error: ";
if (error == "Couldn't resolve host name") {
Message.append("Couldn't connect to the server of ");
if (host.find("google.com") != string::npos) {
Message.append("Google");
}
else {
Message.append("'" + host + "'");
}
}
else {
Message.append("'" + error + "'");
}
cout << Message << endl;
system("pause");
exit(0);
}
There is no CURL method that specifically does this because it can be accomplished by repeated calls to curl_easy_perform.
Here is how you would write the code in your question (the relevant part at least) using loops to retry the CURL request repeatedly:
#include <unistd.h>
#include <curl/curl.h>
/*
* This is the maximum number of times CURL will run
*/
const int max_attempts = 5;
curl = curl_easy_init();
if (curl) {
CurlResponse = "";
host = "http://google.com";
LibcurlHeaders = curl_slist_append(NULL, "Expect:");
if (ProxyAddress.length() > 0) {
curl_easy_setopt(curl, CURLOPT_PROXY, ProxyAddress.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, (host).c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER , 1);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST , 1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, LibcurlHeaders);
for (int i = 1; i <= max_attempts &&
(res = curl_easy_perform(curl)) != CURLE_OK; i++) {
/*
* At this point, you would sleep
* for some seconds between requests
*/
const int sleep_secs = 1;
sleep(sleep_secs);
}
// As others have mentioned, you should delete this line:
//curl_slist_free_all(LibcurlHeaders);
if (res != CURLE_OK) {
// The max retries have all failed
LibcurlError(curl_easy_strerror(res), host);
}
else {
// The request has succeeded in the first `max_retries` attempts
// ...
}
curl_easy_cleanup(curl);
}
curl_global_cleanup();
I am obtaining a HTTP error 400 in response to the folowing RTSP URL being processed by the function shown below.
DESCRIBE rtsp://root:pass#192.168.1.47/axis-media/media.amp ?videocodec=h264/
The IP camera I am using is a recent AXIS H264 camera.
The libcurl version I am using is v7.43.0
bool CHttpClientCurl::Get()
{
// initialize curl
CURLcode res = CURLE_OK;
if (m_curl == NULL)
{
m_sError = L"CURL handle is NULL.";
return false;
}
m_sBuffer.clear();
// initialize this curl session
curl_easy_reset(m_curl);
char sUrl[8192];
wcstombs(sUrl, m_sUrl.c_str(), m_sUrl.length());
sUrl[m_sUrl.length()] = '\0';
curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
printf(" cURL V%s loaded\n", data->version);
if (m_curl != NULL) {
curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(m_curl, CURLOPT_PROTOCOLS, CURLPROTO_RTSP);
res = curl_easy_setopt(m_curl, CURLOPT_URL, sUrl);
// request server options
printf("\nRTSP: OPTIONS %s\n", sUrl);
curl_easy_setopt(m_curl, CURLOPT_PROTOCOLS, CURLPROTO_RTSP);
res = curl_easy_setopt(m_curl, CURLOPT_RTSP_STREAM_URI, sUrl);
curl_easy_setopt(m_curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_DESCRIBE);
curl_easy_setopt(m_curl, CURLOPT_RTSP_TRANSPORT, "RTP/AVP;unicast;client_port=64378-64379");
// curl_easy_setopt(m_curl, CURLOPT_RTSP_SESSION_ID, "56789");
res = curl_easy_perform(m_curl);
int64_t nHttpCode = 0;
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &nHttpCode);
m_nStatusCode = nHttpCode;
if (res != CURLE_OK)
{
std::wstringstream ss;
ss << curl_easy_strerror(res);
m_sError = L"Error occurred - " + std::wstring(ss.str());
return false;
}
else if (nHttpCode != 200)
{
SetErrorString(nHttpCode);
return false;
}
}
return true;
}
Could someone advise me if there is a mistake in the URL or in the C++ function?
Thank you very much.
The way to solve this problem is to first provide user credentials in the rtsp string and second issue a RTSP DESCRIBE twice in a row consecutively. The first RTSP DESCRIBE request will result in a HTTP 401 error code if user credentials are incorrect or missing. The second DESCRIBE request will result in a HTTP 200 OK code. After the second RTSP DESCRIBE returns HTTP 200 OK, you may issue a RTSP SETUP request followed by a RTSP PLAY request which should return HTTP 200 and create the RTSP stream.