CHttp... Post with "action"
I am a newbie to internet programming, so if anyone can help with this, please take me step by step and don't assume I know anything.
There is a web page that I can access on a router (conviently sitting next to me). On it, I am able to "Browse" for a file and then "Load" it. Which actually sends it through a program on the router called "config". "config" will then do stuff to the file and save it wherever and however it needs it.
Now for my problem.
From my Visual C++.NET program, I need to send a file (we'll call it dummyfile.dat) over to the router and have it go through that "config" program.
All I'm trying to do is emulate what they do via HTML on their web page. Here is what I think is the right line from the HTML
<FORM METHOD="POST" ACTION="/cgi-bin/config" enctype="multipart/form-data">
NAME="CONFIG" id="browse"
I've figured out the connection part, but for the request part and on, everything I have tried has failed. If this was a regular, put-the-file-over-there thing, I think I'd be ok. But I just don't get this "run-a-program-over-there" thing.
Someone PLEASE help me! I'm desperate and on a deadline!!!!
Thanks in advance
Re: CHttp... Post with "action"
What happens on the backend (running a program, querying a database, doing nothing, etc) is iirelevant to the http protocol, so don't worry about that.
First, you need to get the full URL to the CGI program. Since the form action is a relative path, it needs to be concatenated with the BaseUrl for the HTML page the form resides in. If there is no explicit BaseUrl set, it defaults to the directory where the HTML page is.
Once you have all the information, you call HttpOpenRequest() on the server, without path and page information.
Then add the HTTP headers. You probably need to add at least this one:
Code:
HttpAddRequestHeaders([handle from HttpOpenRequest], "Content-Type: application/x-www-form-urlencoded", -1L, HTTP_ADDREQ_FLAG_ADD);
Then call HttpSendRequest() with your post data. The data needs to be in
Code:
par1=val1&par2=val2&etc
form.
After HttpSendRequest(), call HttpQueryInfo() on HTTP_QUERY_FLAG_NUMBER|HTTP_QUERY_STATUS_CODE.
Once you follow these steps, tell us where you get errors, if any.
Re: CHttp... Post with "action"
I'm still a little confused. From looking at the html, can you tell what my post data would be? Would it just be the filename I'm trying to post over there?
Here is my code. Does it look right? (Obviously it's not complete because postData is unknown and undeclared)
You rock for even looking at this!
bool CInternetConnectivity::ConnectionTest()
{
return TRUE;
try
{
// Instantiate CInternetSession
CInternetSession httpSession (_T("My Client"),
1,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
INTERNET_FLAG_DONT_CACHE);
// Get CHttpConnection (Server URL and Port required)
CHttpConnection* pHttpConnection =
httpSession.GetHttpConnection(_T("192.168.150.1"),
INTERNET_FLAG_NO_AUTO_REDIRECT,
80, _T("abcd"), _T("1234"));
// Open HTTP Request (pass method type [get/post/..] and URL path (except server name)
CONST TCHAR *szAcceptType = "*/*";
CHttpFile* pHttpFile =
pHttpConnection->OpenRequest
(CHttpConnection::HTTP_VERB_POST,
_T("/cgi-bin/"),
NULL, 1, &szAcceptType, NULL,
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_EXISTING_CONNECT |
INTERNET_FLAG_DONT_CACHE |
INTERNET_FLAG_RELOAD);
// Add HTTP Request Headers
if (!::HttpAddRequestHeaders(pHttpFile,"Content-Type: application/x-www-form-urlencoded", -1L, HTTP_ADDREQ_FLAG_ADD))
{
AfxMessageBox("Problem adding headers[1]");
}
// Send the request
// What would my post data be???
::HttpSendRequest(pHttpFile,NULL, 0, postData, postDataLength);
// Check the return HTTP Status Code
DWORD dwStatusCode = HTTP_STATUS_OK;
pHttpFile->QueryInfoStatusCode(dwStatusCode);
if (dwStatusCode == HTTP_STATUS_OK)
{
}
}
catch(CInternetException* exp)
{
TCHAR lpszErrorMsg[MAX_PATH+2];
exp->GetErrorMessage(lpszErrorMsg, MAX_PATH);
AfxMessageBox(lpszErrorMsg);
}
}
Re: CHttp... Post with "action"
Your code looks correct, although I would pass NULL for username and password in GetHttpConnection(), or leave them out.
I actually lied about the content type header. I just now noticed that the form specification has enctype as "multipart/form-data." I'm not that familiar with this encoding. It's usually used to upload files. Anyways, set your content-type to "multipart/form-data" and read on.
In terms of your post data, you need to look at the form itself. In the HTML page, look for all <INPUT> fields between the <FORM> opening and closing tags. The name of the <INPUT> field will be the name of the URL parameter, and the value will be the value.
So if you have
Code:
<INPUT type="hidden" name="routerConfigMunch" value="yes">
<INPUT type="submit" name="Submit" value="OK">
then this would translate into
Code:
routerConfigMunch=yes&Submit=OK
The names are usually not case-sensitive, but as it depends on the back-end processor (the router), you can't depend on it.
Try this and let us know how it goes.
Re: CHttp... Post with "action"
Ok. I'm not getting very far. I get as far as the HttpAddRequestHeaders(...) call
and it gives me an error.
I do a DWORD errorNum = ::GetLastError(); and it returns a 6
I can't seem to find where these errors are listed to even find out what "6" means!
Any ideas?
Re: CHttp... Post with "action"
#6 means ERROR_INVALID_HANDLE. You can find this out by looking in WinError.h.
I would guess this is because you have to use CHttpConnection instead of CHttpFile. I just noticed this in your code. Actually you should not use CHttpFile at all. Your CHttpConnection, HttpAddRequestHeaders() and HttpSendRequest() should take care of it for you. Sorry I didn't catch this earlier.
Re: CHttp... Post with "action"
Help! I'm still getting error "6". I took out all the CHttpFile stuff and replaced it with my CHttpConnection variable so now it looks like this:
// Instantiate CInternetSession
CInternetSession httpSession (_T("Client"),
1,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
INTERNET_FLAG_DONT_CACHE);
// Get CHttpConnection (Server URL and Port required)
CHttpConnection* pHttpConnection =
httpSession.GetHttpConnection(_T("192.168.150.1"),
INTERNET_FLAG_NO_AUTO_REDIRECT,
80, _T("abcd"), _T("1234"));
CONST TCHAR *szAcceptType = "*/*";
// Add HTTP Request Headers
if (!::HttpAddRequestHeaders(pHttpConnection, szAcceptType, _tcslen(szAcceptType), HTTP_ADDREQ_FLAG_ADD/*REPLACE*/))
{
DWORD errorNum = ::GetLastError();
AfxMessageBox("Problem adding headers[1]");
}
Any ideas why I'm still getting invalid handle?
Ugh.
Re: CHttp... Post with "action"
It's been a while since I used MFC to access WinInet, and maybe our problem is that we are trying to mix MFC with 'raw' WinInet calls. Try this sequence, simplifying the calls a bit (your parameters look fine):
Code:
HINTERNET hconn = InternetConnect(httpSession, [ip], 80, NULL, NULL, INTERNET_SERVICE_HTTP...);
HINTERNET hreq = HttpOpenRequest(hconn, "POST", [cgi-path], ...);
if (!::HttpAddRequestHeaders(hreq, ...))
{
...;
}
We can always replace with MFC calls later, to simplify your code, once we identify the right substitutions.
Re: CHttp... Post with "action"
Also just noticed, you are still using "abcd" and "1234" as user and password. I think you need to remove them. I'm not sure that is causing the problem, but unless that is really a user/password combination required by the router, it's better to leave them out.
Re: CHttp... Post with "action"
Ugh. Here is the lastest code. ( I can't figure out how you put it in those nifty "code" boxes) Now I'm getting a 12150 error when trying to do the headers. I don't even see 12150 in the winerror.h file!!!! Oh, and I do have to pass in a username and password to log in to this stupid router.
If I ever get this to work, I owe you a case of something! Or at least my undying gratitude!
LPCSTR accept = "Accept: */*\r\n";
DWORD errorNum;
HINTERNET pInternet = ::InternetOpen("ParkerVision Client", INTERNET_OPEN_TYPE_PRECONFIG, NULL,
NULL, NULL);
errorNum = ::GetLastError();
HINTERNET httpSession = ::InternetConnect(pInternet, _T("192.168.150.1"), INTERNET_DEFAULT_HTTP_PORT,
_T("admin"), _T("1234"), INTERNET_SERVICE_HTTP, NULL, NULL);
errorNum = ::GetLastError();
HINTERNET pHttpConnection = :: HttpOpenRequest( httpSession, _T("POST"),_T("/cgi-bin/config"),
NULL, NULL, &accept,
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_EXISTING_CONNECT |
INTERNET_FLAG_DONT_CACHE |
INTERNET_FLAG_RELOAD, NULL);
errorNum = ::GetLastError();
// Open HTTP Request (pass method type [get/post/..] and URL path (except server name)
CONST TCHAR *szAcceptType = "*/*";
// Add HTTP Request Headers
if (!::HttpAddRequestHeaders(pHttpConnection, szAcceptType,-1L/* _tcslen(szAcceptType)*/, HTTP_ADDREQ_FLAG_REPLACE))
{
DWORD errorNum = ::GetLastError();
AfxMessageBox("Problem adding headers[1]");
}
Re: CHttp... Post with "action"
I prefer your original code to what it has morphed into. You can use this code of mine to get you started:
CInternetSession iSession("MY_PROGRAM");
CString verb="POST";
CString dest="/cgi-bin/config";
CString HostName="<Destination Host/IP Addr>";
CString retHeader;
CString Data="<Contents of the post>";
bool validResponse=false;
CString header;
header="Content-Type: application/x-www-form-urlencoded\r\n";
header+="Host: ";
header+=HostName;
header+="\r\n";
CHttpConnection *hSession=NULL;
CHttpFile *hFile=NULL;
bool connected=true;
bool sockFail=false;
hSession=iSession.GetHttpConnection(HostName, 0, 80, 0, 0);
if (hSession)
{
hFile=hSession->OpenRequest(verb, dest, 0, 1, 0, "HTTP/1.1", INTERNET_FLAG_RELOAD|INTERNET_FLAG_NO_CACHE_WRITE );
if (hFile)
{
hFile->AddRequestHeaders(header);
if (Data.GetLength()!=0)
{
hFile->SendRequestEx(Data.GetLength());
hFile->WriteString(Data);
hFile->EndRequest();
}
else
{
hFile->SendRequest();
}
hFile->QueryInfo(HTTP_QUERY_STATUS_CODE , retHeader, 0);
if (retHeader!="200")
{
failed=true;
}
else
{
CString buff, Response;
while (hFile->ReadString(buff))
{
Response+=buff;
Response+="\n";
buff.Empty();
}
}
}
}
if (hFile)
{
hFile->Close();
delete(hFile);
}
if (hSession)
{
hSession->Close();
delete (hSession);
}
Let me know if you run into problems.
Thanks,
Rob
Re: CHttp... Post with "action"
Well, you are seeing some progress!
Error codes above 12000 are wininet specific error codes and can be found in WinInet.h. Not easy to know... 12150 is ERROR_HTTP_HEADER_NOT_FOUND.
This makes sense, because you are now passing in "*/*" as an HTTP header. WinInet is trying to tell you that this looks suspiciously incorrect.
If you want to tell the server that you will accept any content type, use this HTTP header:
I still think you need to use the Content-Type header:
Code:
Content-Type: multipart/form-data
Re: CHttp... Post with "action"
Quote:
Originally Posted by HoustonMcDog1
Ugh. Here is the lastest code. ( I can't figure out how you put it in those nifty "code" boxes) Now I'm getting a 12150 error when trying to do the headers. I don't even see 12150 in the winerror.h file!!!!
This error is: "The requested header was not found "
Re: CHttp... Post with "action"
YAY! A Little farther, but not quite....
I get past the headers now. But now when I call HttpSendRequest I get a ERROR_HTTP_INVALID_SERVER_RESPONSE (12152)
First let me show you what the html is on the router side that I'm trying to emulate.
<FORM action="/cgi-bin/config" method="POST" enctype="multipart/form-data">
<p><INPUT TYPE=FILE SIZE=60 NAME="CONFIG" id="browse"></p>
<p><INPUT type="submit" value="Load" id="submit"></p>
</FORM>
Instead of "browsing" to choose a file, I'm just trying to send it the file name that I've already picked.
What am I doing wrong NOW?!?!?
// Add HTTP Request Headers
CString szContentType = "Content-Type: multipart/form-data\r\n";
if (!::HttpAddRequestHeaders(pHttpConnection,"Content-Type: multipart/form-data", _tcslen(szContentType), HTTP_ADDREQ_FLAG_ADD))
{
DWORD errorNum = ::GetLastError();
AfxMessageBox("Problem adding headers[1]");
}
// Send the request
CString postData;
postData = "CONFIG=blah.dat&Load=submit";
long postDataLength = postData.GetLength();
::HttpSendRequest(pHttpConnection, NULL, 0, &postData, postDataLength);
errorNum = ::GetLastError(); //THIS IS WHERE I GET THE SERVER ERROR!!!
Re: CHttp... Post with "action"
Believe it or not, this is actually a good sign. It means we are making progress. The error you are getting is because we didn't post exactly what the server expected, but what is good about it is that you got to the point where you posted something to the router.
Now, in order to upload files, we have to change the format of the posted data. It has to look something like this:
Code:
-----------------------------[hex number]
Content-Disposition: form-data; name="CONFIG"; filename="[your filename]"
Content-Type: [file type]
[file contents here]
-----------------------------[hex number]--
What goes in 'hex number' should be a new number every time -- this helps the server distinguish file uploads. For now, you can use 7d51d919f0276.
You also have to add
Quote:
; boundary=---------------------------7d51d919f0276
to the end of your Content-Type http header, when you add it.
The only other thing that may be in your way is the security on the router. However, I don't think it would give 'invalid response' for that reason. In order to get the security right, you may have to go back and look at the log in form for the router.
Also see this: File Uploads