HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。 HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。
HTTP是基于客户/服务器模式,且面向连接的。典型的HTTP事务处理有如下的过程: [7]
(1)客户与服务器建立连接;
(2)客户向服务器提出请求;
(3)服务器接受请求,并根据请求返回相应的文件作为应答;
(4)客户与服务器关闭连接。
客户与服务器之间的HTTP连接是一种一次性连接,它限制每次连接只处理一个请求,当服务器返回本次请求的应答后便立即关闭连接,下次请求再重新建立连接。这种一次性连接主要考虑到WWW服务器面向的是Internet中成千上万个用户,且只能提供有限个连接,故服务器不会让一个连接处于等待状态,及时地释放连接可以大大提高服务器的执行效率。
报文格式
HTTP报文由从客户机到服务器的请求和从服务器到客户机的响应构成。请求报文格式如下: [6]
请求行 - 请求头 - 报文主体
请求行以方法字段开始,后面分别是URL字段和HTTP协议版本字段,并以CRLF结尾。SP是分隔符。除了在最后的CRLF序列中CF和LF是必需的之外,其他都可以不要。有关通用信息头,请求头和实体头方面的具体内容可以参照相关文件。
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
请求行:
其中“请求行”为:请求行 = 方法 [空格] 请求URI [空格] 版本号 [回车换行]
如:1. GET /index.html HTTP/1.1
2. POST http://192.168.2.217:8080/index.jsp HTTP/1.1
这个在具体函数就是添加头信息,如
1.在wininet 类中,请求行
std::string header((type == HttpPost) ? "POST " : "GET ");
header += request_page_;
header.append(" ");
header += http_version_;
header.append(http_newline);
即 GET /hdhdhd HTTP/1.1
请求头:
HTTP最常见的请求头如下:
Accept:浏览器可接受的MIME类型;
Accept-Charset:浏览器可接受的字符集;
Accept-Encoding:浏览器能够进行解码的数据编码方式,比如gzip。Servlet能够向支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间;
Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到;
Authorization:授权信息,通常出现在对服务器发送的WWW-Authenticate头的应答中;
Connection:表示是否需要持久连接。如果Servlet看到这里的值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。要实现这一点,Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入ByteArrayOutputStream,然后在正式写出内容之前计算它的大小;
Content-Length:表示请求消息正文的长度;
Cookie:这是最重要的请求头信息之一;
From:请求发送者的email地址,由一些特殊的Web客户程序使用,浏览器不会用到它;
Host:初始URL中的主机和端口;
If-Modified-Since:只有当所请求的内容在指定的日期之后又经过修改才返回它,否则返回304“Not Modified”应答;
Pragma:指定“no-cache”值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝;
Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。
User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用;
UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE浏览器所发送的非标准的请求头,表示屏幕大小、颜色深度、操作系统和CPU类型。
Allow:GET,POST
Content-Encoding:文档的编码(Encode)方法,例如:gzip,见“2.5 响应头”;
Content-Language:内容的语言类型,例如:zh-cn;
Content-Length:表示内容长度,eg:80,可参考“2.5响应头”;
Content-Location:表示客户应当到哪里去提取文档,例如:http://www.dfdf.org/dfdf.html,可参考“2.5响应头”;
Content-MD5:MD5 实体的一种MD5摘要,用作校验和。发送方和接受方都计算MD5摘要,接受方将其计算的值与此头标中传递的值进行比较。Eg1:Content-MD5: <base64 of 128 MD5 digest>。Eg2:dfdfdfdfdfdfdff==;
Content-Range:随部分实体一同发送;标明被插入字节的低位与高位字节偏移,也标明此实体的总长度。Eg1:Content-Range: 1001-2000/5000,eg2:bytes 2543-4532/7898
Content-Type:标明发送或者接收的实体的MIME类型。Eg:text/html; charset=GB2312 主类型/子类型;
Expires:为0证明不缓存;
Last-Modified:WEB 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified:Tue, 06 May 2008 02:42:43 GMT.
Accept: */*
Accept-Language: zh-CN,zh;q=0.9
Connection: Keep-Alive
Host: www.hbsrsksy.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.4044.92 Safari/537.36
address: Shanghai
name: Jelin
//注意结尾有两个\r\n
1.在wininet 类中,
调用HttpAddRequestHeadersA,从而添加请求行、请求头。
如:
std::string httpHeaders = m_header.toHttpHeaders(); //httpHeaders 即是上面的请求行加上请求头的字符集合
HttpAddRequestHeadersA(m_hRequest, httpHeaders.c_str(), httpHeaders.size(), HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
调用HttpSendRequestA,从而请求发送数据。(注意:当是GET的方式,表明不需要请求发送任何数据,那么该函数的第四个参数的缓冲区为空,如lpPostData = null。当是POST的方式,该函数的第四个参数的缓冲区为发送的数据,其格式可以为json格式,如lpPostData = "{\"name\":\"Jelin\",\"address\":\"Shanghai\"}")
如:
bRet = HttpSendRequestA(m_hRequest, lpHeader, dwHeaderSize, (LPVOID)lpPostData, dwSize);
2.在winhttp中,是调用WinHttpAddRequestHeaders,从而添加请求行、请求头、实体头。调用WinHttpSendRequest,从而请求发送数据。
std::wstring header = A2U(m_header.toHttpHeaders());
::WinHttpAddRequestHeaders(m_hRequest, header.c_str(), header.size(), WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
DWORD dwSize = (NULL == lpPostData) ? 0 : strlen(lpPostData);
if (lpHeader == NULL)
return ::WinHttpSendRequest(m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)lpPostData, dwSize, dwSize, NULL) == TRUE;
return ::WinHttpSendRequest(m_hRequest, lpHeader, -1L, (LPVOID)lpPostData, dwSize, dwSize, NULL) == TRUE;
3.在socket 中,是只调用send,同时发送请求行、请求头、实体头和数据(如果是POST的话),当然如果是GET,也不用发送数据)。
如当是POST的方式,该strSend 包含发送的数据
char len[10] = {0};
sprintf(len, "%d", data.length());
std::string strLen = len;
// "POST /[req] HTTP/1.1\r\n"
// "Connection:Keep-Alive\r\n"
// "Accept-Encoding:gzip, deflate\r\n"
// "Accept-Language:zh-CN,en,*\r\n"
// "Content-Length:[len]\r\n"
// "Content-Type:application/x-www-form-urlencoded; charset=UTF-8\r\n"
// "User-Agent:Mozilla/5.0\r\n\r\n"
// "[data]\r\n\r\n";
std::string strSend = " HTTP/1.1\r\n"
"Cookie:16888\r\n"
"Content-Type:application/x-www-form-urlencoded\r\n"
"Charset:utf-8\r\n"
"Content-Length:";
strSend = "POST " + req + strSend + strLen + "\r\n\r\n" + data;
int nRet = send(m_socket, strSend.c_str(), strSend.size(), 0);
应答报文格式如下:
状态行 - 响应头 - - 报文主体
状态码元由3位数字组成,表示请求是否被理解或被满足。原因分析是对原文的状态码作简短的描述,状态码用来支持自动操作,而原因分析用来供用户使用。客户机无需用来检查或显示语法。有关通用信息头,响应头和实体头方面的具体内容可以参照相关文件。
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
状态行
其中:状态行 = 版本号 [空格] 状态码 [空格] 原因 [回车换行]
如
1.HTTP/1.0 200 OK
2.HTTP/1.1 400 Bad Request
响应头:
HTTP最常见的响应头如下所示:
Allow:服务器支持哪些请求方法(如GET、POST等);
Content-Encoding:文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept-Encoding"))检查浏览器是否支持gzip,为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面;
Content-Length:表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入ByteArrayOutputStram,完成后查看其大小,然后把该值放入Content-Length头,最后通过byteArrayStream.writeTo(response.getOutputStream()发送内容;
Content-Type: 表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。由于经常要设置Content-Type,因此HttpServletResponse提供了一个专用的方法setContentTyep。 可在web.xml文件中配置扩展名和MIME类型的对应关系;
Date:当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦;
Expires:指明应该在什么时候认为文档已经过期,从而不再缓存它。
Last-Modified:文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置;
Location:表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302;
Refresh:表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。注意Refresh的意义是“N秒之后刷新本页面或访问指定页面”,而不是“每隔N秒刷新本页面或访问指定页面”。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。
函数
1. GET:向特定的资源发出请求
2.POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
GET与POST方法有以下区别:
(1) 在客户端,Get方式在通过URL提交数据,数据在URL中可以看到;POST方式,数据放置在HTML HEADER内提交。
(2) GET方式提交的数据最多只能有1024字节,而POST则没有此限制。
(3) 安全性问题。正如在(1)中提到,使用 Get 的时候,参数会显示在地址栏上,而 Post 不会。所以,如果这些数据是中文数据而且是非敏感数据,那么使用 get;如果用户输入的数据不是中文字符而且包含敏感数据,那么还是使用 post为好。
(4) 安全的和幂等的
具体实现办法
如果在window平台上,可以使用win32网络 api函数实现。为实现Http访问,微软提供了二套API:WinINet, WinHTTP。
在一个Session之上可以存在多个Connection对象,每个Connection对应于一个目标服务器和端口号。在一个Connection之上可以存在多个Request对象,每个Request对应于Connection之上的URI资源地址以及HTTP动作(GET或POST等)。
网络交互是一个耗时的操作,针对这一特点,WinInet和WinHttp作为Htpp协议的Windows网络实现库,提供了两种编程模型:即同步编程模型和异步编程模型。同步编程模型可以理解为在调用API的线程中启动网络请求并进入到阻塞状态,当网络请求返回或者内部发生错误时,阻塞被解除,程序继续顺序执行。而在异步编程模型中,当调用API的线程发出请求时,在WinInet或WinHttp模块内部会启动一个工作线程来完成耗时的网络操作,请求的结果通过回调报告给调用者线程所注册的函数,但是回调函数的执行环境还是在工作线程中,所以需要对临界区做互斥保护。异步编程模型比同步编程模型更灵活,工作线程的管理工作也在模块内部被透明的完成,譬如同时启动大量的请求,WinInet和WinHttp都会将请求排队,而不是立即启动数量相同的工作线程,破坏系统的整体稳定性。
WinHTTP比WinINet更加安全和健壮,可以这么认为WinHTTP是WinINet的升级版本。这两套API包含了很多相似的函数与宏定义。下面的例子都是基于WinINet实现的。
1.WinInet是windows平台对socket进行一层封装,用来直接处理http/ftp/Gopher协议的一套windows AP。
具体见:Win32 网络API InternetOpen\InternetOpenUrl\InternetReadFile 等_baidu_16370559的博客-CSDN博客 和c++ http请求,json解析_少年夜雨行的专栏-CSDN博客
2.WinHTTP。结论是推荐使用WinHttp来代替WinInet。WinHttp_baidu_16370559的博客-CSDN博客
3.使用套接字socket。其和原始的socket没多大的区别,主要是send函数的发送缓冲区多添加一些请求行、请求头、实体头的信息。
例子见:C++ 一个简单的实现HTTP GET和POST请求的类_S00000NG的笔记-CSDN博客_c++ get post