摘要:HTTP协议又叫超文本传输协议,所有的www文件都必须遵循这个协议。当互联网诞生时,HTTP协议被设计的是从网络上获取数据(客户主动获取数据),一旦服务器对其请求处理之后,浏览器就会断开这条连接。如此,可以想象HTTP是不支持服务端主动推送消息给浏览器的。如果想了解更多推送相关的知识可以阅读《Websocket轻量级消息推送 & 浏览器socket通信》一文,主要阐述了服务端要如何解析Websocket协议,另外可以阅读《古老浏览器与Flash socket通信》一文,它阐述了Flash(也就是swf)如何与js通信,Flash如何通过socket与远程跨域通信。而本文的主要内容是:使用socket(TCP连接)实现HTTP协议并充当服务端,对ajax的HTTP请求进行处理。
目录:
-------------------------------------------
- No Web Server
- ajax GET请求
- socket recv数据包
- ajax 跨域及TCP解析HTTP
- POST的缺陷
1.No Web Server
在记忆当中,浏览器的HTTP请求总是伴随着tomcat、nginx、jboss、weblogic、websphere之类的特别配置过的电脑作为服务器,以快速高效的处理HTTP请求。那么,如果电脑上(如客户机)无法安装类似的服务是不是就无法处理HTTP请求了?其实这样从HTTP协议讲起:
HTTP协议是应用层协议,在传输层是通过TCP来保持数据的可靠性、同时保证数据到达的顺序性。如此,可以知道HTTP请求实质上是一条TCP连接,再加上在其之上约定的传输协议(也就是HTTP)。这套传输协议,具体来说就是HTTP headers。而headers一般又分为两个部分:request和response,
2. ajax GET请求
上述截图,是基于ajax采用GET提交数据(同步ajax): http://127.0.0.1:8086/index.html?name=qingdujun&age=18
<html>
<head>
<script type="text/javascript">
function loadXMLDoc()
{
var xmlhttp;
if (window.XMLHttpRequest){// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}else{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("GET","http://127.0.0.1:8086/index.html?name=qingdujun&age=18",false);
xmlhttp.send();
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
</script>
</head>
<body>
<h2>AJAX</h2>
<button type="button" οnclick="loadXMLDoc()">请求数据</button>
<div id="myDiv"></div>
</body>
</html>
3. socket recv数据包
服务端其实就是一个socket,recv的数据是这样的,直接打印了出来。下图中,大部分都是HTTP headers中的协议,实际需要处理的内容就是第一行。根据这些数据,采用socket实现HTTP就有思路了,只需要将第一行的GET内容进行切割,识别出需要的内容,即实现了数据的接受。那么如何发送数据呢?更简单了,只需要将HTTP headers附加到数据包中,然后通过TCP发送给浏览器即可。
4.ajax 跨域及TCP解析HTTP
这里贪图方便,直接使用了网上现成的C#书写的服务端,具体可以阅读《C#中使用Socket实现简单Web服务器》一文。当然,进行了些许的改动,主要是针对ajax跨域请求的:Access-Control-Allow-Origin:*
附上修改后的socket实现HTTP Code,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
namespace socket2http
{
class Program
{
static Socket m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
static void OnAccept(IAsyncResult ar)
{
try
{
Socket socket = ar.AsyncState as Socket;
Socket new_client = socket.EndAccept(ar);
socket.BeginAccept(new AsyncCallback(OnAccept), socket);
byte[] recv_buffer = new byte[1024 * 640];
int real_recv = new_client.Receive(recv_buffer);
string recv_request = Encoding.UTF8.GetString(recv_buffer, 0, real_recv);
Console.WriteLine(recv_request);
Resolve(recv_request, new_client);
}
catch
{
}
}
static void Resolve(string request, Socket response)
{
string[] strs = request.Split(new string[] { "\r\n" }, StringSplitOptions.None);
if (strs.Length > 0)
{
string[] items = strs[0].Split(' ');
Dictionary<string, string> param = new Dictionary<string, string>();
if (strs.Contains(""))
{
string post_data = strs[strs.Length - 1];
if (post_data != "")
{
string[] post_datas = post_data.Split('&');
foreach (string s in post_datas)
{
param.Add(s.Split('=')[0], s.Split('=')[1]);
}
}
}
Route(items[1], param, response);
}
}
public static void HomePage(Socket response)
{
string statusline = "HTTP/1.1 200 OK\r\n";
byte[] statusline_to_bytes = Encoding.UTF8.GetBytes(statusline);
string content ="abcdef";
byte[] content_to_bytes = Encoding.UTF8.GetBytes(content);
string header = string.Format("Access-Control-Allow-Origin:*\r\nContent-Type:text/html;charset=UTF-8\r\nContent-Length:{0}\r\n", content_to_bytes.Length);
byte[] header_to_bytes = Encoding.UTF8.GetBytes(header);
response.Send(statusline_to_bytes);
response.Send(header_to_bytes);
response.Send(new byte[] { (byte)'\r', (byte)'\n' });
response.Send(content_to_bytes);
response.Close();
}
static void Route(string path, Dictionary<string, string> param, Socket response)
{
HomePage(response);
return;
if (path.EndsWith("index.html") || path.EndsWith("/"))
{
}
}
static void Main(string[] args)
{
m_socket.Bind(new IPEndPoint(IPAddress.Any, 8086));
m_socket.Listen(100);
m_socket.BeginAccept(new AsyncCallback(OnAccept), m_socket);
Console.Read();
}
}
}
5.POST的缺陷
另外,《关于http post两阶段提交的一些问题》一文中提到:“有不少浏览器厂商对于POST的提交采用两阶段发送数据,特别是IE系统列的XMLHttpRequest对象,所以在IE浏览器上用AJAX提交POST的数据,是按两个阶段,第一步先发送header数据,第二步再发送body部分。如果我们用winshark抓包会看到两连次结过程。而对于firefox浏览器,则采用一次连接,这也是原来HTTP协议的“本意”,http协议本身不保存任何状态信息,一次请求一次应答。对于TCP事务而言,通讯次数越多可靠性越低,在一次连结中传输完需要的消息是最可靠的,但是却有很多浏览器厂商不愿意遵守这个原则,它们的理由也很搞笑:假如网络环境不好,网络延迟、丢包的时候,服务端会等待(延迟时),客户端重发POST的DATA数据到服务单,来确保本次请求的完整性。”
所以,鉴于上述基础上,ajax请求能使用GET方式的坚决不用POST。
@qingdujun
2017-7-23 in Xi'An