初学者 HTTP 调用入门






4.43/5 (11投票s)
对 HTTP 调用各个组成部分的初学者解释。
引言
最近有几篇文章让我觉得有必要对基本的 HTTP 调用包含哪些内容进行初学者介绍。在此,我将解释文本行和实际发送的消息的基本组成部分,并提供一些示例代码供您尝试。
提供的示例旨在提供一个起点供您查看。提交的代码摘录自我用于测试 SOAP 调用到服务的代码。它使用了 MFC 的 CInternetSession
类。
文章中的服务器端代码(不在下载中)是通用的 C 代码。该服务器代码在 Apache Server (1.3.19) 和 MS 的 IIS 服务器下作为 CGI 应用程序运行,没有进行任何修改。
URL 和建立连接的基本组成部分。
在我们开始讨论代码之前,让我们花点时间达成共识。我们必须了解客户端用来建立服务器连接的内容以及实际发送的内容。以下行是我输入到浏览器中的内容
https://:680/cgi/cgimfc1?MyInfo=data1+AndMoreInfo=data2
在服务器处理完请求后,我的浏览器显示了以下内容
Hello Me
Your First Line
This is more text There are 1 Arguments here
The Query String is MyInfo=data1 AndMoreInfo=data2
The Server Soft is Apache/1.3.19 (Win32)
The SERVER_PORT is 680
The REQUEST_METHOD is GET
The REMOTE_USER is
The CONTENT_TYPE is
The CONTENT_LENGTH is
OR The CONTENT_LENGTH is 0
The windir is C:\WINNT
The DeCode length is 0
现在发生了什么?哪些部分参与其中?首先,让我们看看我们输入的这行。段说明传输
http |
告诉浏览器要建立哪种类型的连接。这不是传输的信息。 |
:// |
分隔符 结束连接类型定义 |
localhost |
告诉浏览器要与之通信的机器的地址。除了建立连接之外,这也不是传输的信息。 |
: |
如果给出了端口号,分隔符就是可选的。默认的 http 端口号是 80 |
680 |
告诉浏览器要向服务器上的哪个端口发出连接请求。除了建立连接之外,这也不是传输的信息。 |
/ |
分隔符 终止连接信息 |
Cgi/cgimfc1 |
告诉浏览器要运行的进程(或要返回的页面)。这是传输数据的一部分 |
? |
分隔符 可选 结束进程定义 |
MyInfo=data1+AndMoreInfo=data2 |
服务器可能用于处理请求的查询信息。这是传输数据的一部分。这称为查询 |
发送的内容
GET /cgi/cgimfc1?MyInfo=data1+AndMoreInfo=data2 HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, application/pdf, */* Accept-Language: en-us Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0) Host: localhost:680 Connection: Keep-Alive
对发送内容的评论
第一行将以 GET 或 POST 开头。后面的行可以通过环境变量调用获得。这是 Web 服务器软件提供的一项重要服务。所发送数据基本块的最后一行是“Content Length”(未显示,因为没有附加数据),如果传输中包含附加数据。传输的附加数据的长度就是这一行的值。然后用两个 CR/LF 对终止此行。之后是附加数据。由于我们刚刚从浏览器发送了一个请求,所以没有这个附加数据。它将由脚本或程序生成的 http 调用创建。
返回的内容
HTTP/1.1 200 OK Date: Thu, 14 Jun 2001 18:40:09 GMT Server: Apache/1.3.19 (Win32) Transfer-Encoding: chunked Content-Type: text/html 1eb <HTML><BODY>Hello Me<br><br>Your First Line<br><br> This is more text There are 2 Arguments here <br><br> The Query String is MyQuery=QData<br><br> The Server Soft is Apache/1.3.19 (Win32)<br><br> The SERVER_PORT is 980<br><br> The REQUEST_METHOD is POST<br><br> The REMOTE_USER is <br><br> The CONTENT_TYPE is <br><br> The CONTENT_LENGTH is 23<br><br> OR The CONTENT_LENGTH is 23<br><br> The windir is C:\WINNT<br><br> The DeCode length is 21<br><br> This is Data to Send!<br><br> </BODY></HTML>
对返回内容的评论。
请注意,报头数据由接收程序读取,其中包含有关如何处理请求以及可能适用的任何错误消息的信息。在此信息之后是浏览器显示的 HTML 数据。
服务器 CGI 程序。
Apache 和 IIS 都支持 HTTP 服务器运行服务器下的命令行程序作为 CGI 应用程序。(对于非 NT 机器的用户,Apache 可以作为应用程序安装并运行,以提供学习环境。)通过这样做,服务器会设置 CGI 程序可以请求的环境变量,并且“标准输入”指向与 HTTP 调用一起发送的附加数据流(这通常与 POST 一起使用。)GET 调用也可以这样做,但许多服务器的缓冲区相当小。“标准输出”指向要发送回的数据流。因此,CGI 程序只需要是一个命令行应用程序,它可以读取所需的环境变量,如果需要,还可以读取附加数据流并执行所需的操作。这包括在调用出现故障时返回数据!
我们的第一个 CGI 程序最好通过尝试获取可用数据来理解环境变量和读取附加数据。我们在学习,对吧。
我们启动 Visual Studio 环境,并请求创建一个新的 Win32 应用程序。
我们将它命名为 Cgi1。我选择包含 MFC,因为我喜欢 CString
类。需要注意不要使用未处理的部分,以免引起用户消息,当没有操作员可用时,这会让服务器处于等待状态!也就是说,这不适合生产代码!
您可以选择将根入口点设置为 int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
函数,或者只使用标准的 main 函数。我选择使用标准的 main 函数,因为它更具可移植性。当然,MFC 的使用需要被移除。我的整个程序(不包括辅助编码函数)是
// Cgi1.cpp : Defines the entry point for the console // application. // #include <stdio.h> #include "stdafx.h" #include "Cgi1.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////// // The one and only application object CWinApp theApp; using namespace std; int main(int argc, TCHAR* argv[]) { int nRetCode = 0; printf("Content-type: text/html\n\n"); FILE *stream; stream = fopen( "CGITest.txt", "w" ); printf("<HTML><BODY>"); printf("Hello Me<br><br>"); printf("Your First Line<br><br>\n"); cout << "This is more text<br><br>" << endl; CString Line; Line.Format("There are %d Arguments here <br><br>\n",argc); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); // Enviroment Variables CString path; path = DeCode(getenv("QUERY_STRING")); Line.Format("The Query String is %s<br><br>\n",path); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); path = getenv("SERVER_SOFTWARE"); Line.Format("The Server Soft is %s<br><br>\n",path); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); path = getenv("SERVER_PORT"); Line.Format("The SERVER_PORT is %s<br><br>\n",path); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); path = getenv("REQUEST_METHOD"); Line.Format("The REQUEST_METHOD is %s<br><br>\n",path); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); path = getenv("REMOTE_USER"); Line.Format("The REMOTE_USER is %s<br><br>\n",path); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); path = getenv("CONTENT_TYPE"); Line.Format("The CONTENT_TYPE is %s<br><br>\n",path); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); path = getenv("CONTENT_LENGTH"); Line.Format("The CONTENT_LENGTH is %s<br><br>\n",path); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); long ldata = atol((LPCSTR)path); Line.Format("OR The CONTENT_LENGTH is %d<br><br>\n",ldata); printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); path = getenv("windir"); Line.Format("The windir is %s<br><br>\n",path); printf((LPCSTR)Line); // Now Read the Data Block in from stdin if data has been // labeled as existing. Line=""; CString xstr; if(ldata>0) { fread(xstr.GetBuffer(ldata),sizeof(char),ldata,stdin); xstr.ReleaseBuffer(); Line = DeCode(xstr); } xstr.Format("The DeCode length is %d<br><br>\n",Line.GetLength()); printf((LPCSTR)xstr); fprintf( stream, "%s\n",xstr); Line+="<br><br>\n"; printf((LPCSTR)Line); fprintf( stream, "%s\n",Line); printf("</BODY></HTML>"); fclose( stream ); return nRetCode; }
我在上面的代码中同时使用了“printf
”函数和 cout <<
运算符。
此程序实际返回的文本是
HTTP/1.1 200 OK
Date: Thu, 14 Jun 2001 18:40:09 GMT
Server: Apache/1.3.19 (Win32)
Transfer-Encoding: chunked
Content-Type: text/html
1eb
<HTML><BODY>Hello Me<br><br>Your First Line<br><br>
This is more text<br><br>
There are 2 Arguments here <br><br>
The Query String is MyQuery=QData<br><br>
The Server Soft is Apache/1.3.19 (Win32)<br><br>
The SERVER_PORT is 980<br><br>
The REQUEST_METHOD is POST<br><br>
The REMOTE_USER is <br><br>
The CONTENT_TYPE is <br><br>
The CONTENT_LENGTH is 23<br><br>
OR The CONTENT_LENGTH is 23<br><br>
The windir is C:\WINNT<br><br>
The DeCode length is 21<br><br>
This is Data to Send!<br><br>
</BODY></HTML>
报头由服务器提供。然后,它添加双 cr/lf,然后在下一行添加返回缓冲区的大小。之后是您的程序输出的文本。您的程序必须将内容类型作为程序的第一个输出提供,以便服务器使用。之后,如果您希望使用浏览器测试结果,则需要将其余文本包装在 <HTML> 和 <BODY> 标记中。我发现这很有用。当您对数据满意后,就可以取消注释这些行继续进行。
这里需要注意的关键项是获取环境变量的调用以及您可能需要的变量名称。调用是 GET 还是 POST 重要吗?记录端口重要吗?“查询字符串”是什么?附加数据有多长(如果有的话)?然后读取数据并进行处理。
客户端程序(下载)
为了开始,我们只需要一些基础功能,所有其他用途都可以基于这些功能来实现。我们还必须能够查看我们的数据,以了解真正发送的内容。Web 浏览器是一个起点,但我们也需要了解浏览器为了创建到服务器的调用而进行了多少处理。标准 URL 中的几个项目永远不会发送,但它们被用来建立到服务器的连接。为了帮助阐明这一点,我创建了一个 C 函数,该函数接受所需的组件,然后进行请求的处理。然后,该函数可以在我们选择的任何应用程序中使用。
为了展示此用法,我将创建一个简单的对话框应用程序,其中包含每个有意义项的编辑字段。
建立连接需要服务器地址和端口。这些数据实际上从未发送到服务器。一旦连接建立,您就必须打包要发送的数据。实际发送的数据如下
POST /cgi-bin/CgiMfc1?MyQuery%3dQData HTTP/1.1
Accept: text/*
User-Agent: HttpCall
Accept-Language: en-us
Host: localhost:680
Content-Length: 98
000629EF2656+vs+%0d%0a143%2e114%2e37%2e22+vs+%0d%0aDEKGKIEG+vs+
%0d%0a000629ef2656+vs+%0d%0a2051586
演示中用于进行调用的代码是
void CHttpAccessDemoDlg::OnSubmit() { CWnd* pWndRet = GetDlgItem(IDC_ReturnData); UpdateData(TRUE); m_ReturnedData=""; CMemFile MemFile; int RetVal = HttpCall((LPCSTR)m_ServerAddress,m_ServerPort, (LPCSTR)m_ServerProcess,(LPCSTR)m_QueryString, (LPCSTR)m_DataBlock,&MemFile,pWndRet); int DataLen = MemFile.GetLength(); MemFile.SeekToBegin(); MemFile.Read(m_ReturnedData.GetBuffer(DataLen),DataLen); m_ReturnedData.ReleaseBuffer(); UpdateData(FALSE); }
它调用通用函数“HttpCall”,在示例中,该函数使用 MFC 的 CHttpSession
和相关类。
int HttpCall(const char* ServerAddress, int ServerPort, const char* ServerProcess, const char* QueryString, const char* DataBlock, CFile* DataFile, CWnd* pStatusWnd) { CString strUser; strUser = GetTheUserName(); long lread,max; // No Need to parse URL we create entities themselves. DWORD ServiceType = INTERNET_SERVICE_HTTP; CString UserName="anonymous"; CString UserPassWord=""; CString lf; lf.Format("%c%c",char(13),char(10)); // need unix line feed CString EnCodeData = EnCodeStr(DataBlock); // // Setup Server DWORD AccessType = PRE_CONFIG_INTERNET_ACCESS; CString UserDef = "HttpCall"; // User Application CHttpSession* pHttpSession = new CHttpSession(UserDef,1,AccessType); pHttpSession->SetStatusWnd(pStatusWnd); CHttpConnection* pHttpConnection = NULL; pHttpConnection = pHttpSession-> GetHttpConnection(ServerAddress,ServerPort, UserName,UserPassWord); CHttpFile* pHttpFile = NULL; DWORD HttpRequestFlags; // CString Process,Query; Process = ServerProcess; Query = QueryString; if(Query.GetLength()>0) Process += '?'+EnCodeStr(Query); // Open the request and send it; // if the request has an invalid port it fails. // Need to look for option for error test. TRY { HttpRequestFlags = INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE; DWORD TotalLen; TotalLen = EnCodeData.GetLength(); if(TotalLen>0) { pHttpFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST, Process, NULL, 1, NULL, (LPCTSTR)"1.0", HttpRequestFlags); } else { pHttpFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_GET, Process, NULL, 1, NULL, (LPCTSTR)"1.0", HttpRequestFlags); } // Use direct write to posting field! CString strHeaders = "Accept: text/*\r\n"; strHeaders += "User-Agent: HttpCall\r\n"; strHeaders += "Accept-Language: en-us\r\n"; // strHeaders += "Content-type: application/x-www-form-urlencoded\r\n"; // strHeaders += "REMOTE_USER: "+strUser+"\r\n"; // strHeaders += "Accept-Language: en-us\r\n"; pHttpFile->AddRequestHeaders((LPCSTR)strHeaders); if(TotalLen>0) { pHttpFile->SendRequestEx(TotalLen,HSR_INITIATE,1); pHttpFile->WriteString((LPCTSTR)EnCodeData); pHttpFile->EndRequest(); } else { pHttpFile->SendRequest(); } max = pHttpFile->GetLength(); } CATCH_ALL(e) { AfxMessageBox("Connection to Server Failed"); } END_CATCH_ALL DWORD dwRet; pHttpFile->QueryInfoStatusCode(dwRet); // //Check Return Code. DataFile->SeekToBegin(); // Read Data CString strRetBufLen; pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH, strRetBufLen); max = atol((LPCSTR)strRetBufLen); if(max<=0) { max = pHttpFile->GetLength(); } // Read Data lread=1000; char c[1000]; while(lread>0) { lread = pHttpFile->Read(c,1000); if(lread>0) DataFile->Write(c,lread); } pHttpFile->Close(); pHttpConnection->Close(); pHttpSession->Close(); delete pHttpFile; delete pHttpConnection; delete pHttpSession; return dwRet; }
通用辅助函数
CString EnCodeStr(CString ToCode) { CString RetStr,AddStr; int i,max; unsigned short asc; unsigned char c; max = (unsigned int)ToCode.GetLength(); for(i=0;i<max;i++) { c = ToCode[i]; asc = c;//(unsigned int)c; if(asc>47 && asc<58) { RetStr+=c; } else if(asc>64 && asc<91) { RetStr+=c; } else if(asc>96 && asc<123) { RetStr+=c; } else if(asc==32) { RetStr+="+"; } else { AddStr.Format("%%%2x",asc); int iv = (int)AddStr.GetAt(1); if((int)AddStr.GetAt(1)==32) { AddStr.SetAt(1,'0'); } RetStr+=AddStr; } } return RetStr; } CString DeCodeStr(CString ToCode) { CString RetStr,AddStr; int i,max; unsigned short asc; unsigned char c; max = (unsigned int)ToCode.GetLength(); for(i=0;i<max;) { c = ToCode[i]; asc = c;//(unsigned int)c; if(asc==37) { AddStr=ToCode.Mid(i+1,2); i+=3; sscanf((LPCTSTR)AddStr,"%2x",&asc); RetStr+=(char)asc; } else if(asc==43) { RetStr += ' '; i++; } else { RetStr += c; i++; } } return RetStr; }