从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换

项目仓库 GitHub

请求url处理

  • url有两种,一种是通过域名,一种直接ip
    • 直接ip常用于客户端的api调用,或者是在测试环境
    • 域名一般是ajax调用api,或者是网页的获取,有几个好处,ip可能会变但网址一般不变,可以不用修改。并且可以起到负载均衡的作用,无论dns还是nginx.

请求url字符串必须以协议名开头,协议名开头,c#,java的httpclient都是这样没记错的话,至于协议现在只支持http.https等我写完服务器再来搞

			MyString protocolStr = "http://";
			if (url.find(protocolStr)!=0)
			{
				MyString Msg = "标明协议!仅允许使用" + protocolStr;
				throw exception(Msg.c_str());
			}

对于http协议,host和请求路径是分开的,所以需要对url进一步拆分

			url.erase(0, protocolStr.length());//移除协议名称
			MyString host = url.substr(0, url.find("/"));
			Header["Host"] = host;
			url.erase(0, host.length());
			Path = url.length()!=0?url:"/";//在前面已经将http协议头和主机名ip移去
			Socket socket = GetSocket();
			//getsocket函数已修改,默认获取客户端socket,客户端的端口由系统临时分配不需要指定
			//若指定端口号则认为在获取一个服务器socket
			Connection(socket,host);

connection仅用于请求,连接目标服务器端口

若在host中有:,则修改端口默认80
若在host中有字母,则通过GetIpByHost获取ip
目前仅考虑ipv4,ipv6等ssl后再来

		void Connection(Socket socket,MyString host)
		{
			int port = 80;//没有:8080,直接80就行
			int indexPort = host.find(":");
			if (indexPort!=-1)
			{//127.0.0.1:5000,改端口为5000
				port = stoi(host.substr(indexPort+1));
				host = host.substr(0, indexPort);//去掉端口
			}
			in_addr sa;
			MyString ServerAddr = //包含字母是网址,没有ip
				host.HasLetter() ? GetIpByHost(host) : host;
			if (InetPton(AF_INET, ServerAddr.c_str(), &sa) == -1)
			{
				throw new exception("地址转换错误");
			}
			sockaddr_in saddr;
			saddr.sin_family = AF_INET;
			saddr.sin_port = htons(port);
			saddr.sin_addr = sa;
			if (::connect(socket, (sockaddr*)&saddr, sizeof(saddr)) != 0)
			{
				MyString Msg = "连接失败错误码:" + WSAGetLastError();
				throw new exception(Msg.c_str());
			}
		}

响应读取

在connection后,就可以发送等待接收了
接受到的流使用mystring托管
recv的返回值,0为接受结束,大于0为收到的字节数,小于0是错误,
这里使用阻塞接受,因为不久后改成异步,所有耗时长的io都必须异步

			BuildRequestString();//把请求的各个属性转换成字符串
			send(socket, Source.c_str(), Source.length(), 0);
			//读取响应由,Response托管
			int num = 0;
			char buf[1024] = {0};
			Response resp;
			num = recv(socket, buf, sizeof(buf) - 1, 0);
			resp.Source += buf;
			resp.ParseFromSource();
			int lenHeader = resp.Source.length() - resp.ResponseBody.length();
			int len = resp.Length;//解析后在响应头里的正文长度
			while (resp.Source.length()-lenHeader<len)
			{////接收到的字符少于缓冲区长度,接收结束,也有可能是错误或者连接关闭
				num = recv(socket, buf, sizeof(buf) - 1, 0);
				resp.Source += buf;
				memset(buf, 0, sizeof(buf));
				if (num < 0)
				{
					MyString Msg = "网络异常,Socket错误码:" + num;
					throw exception(Msg.c_str());
				}
			}

接受字节数为什么是sizeof(buf) - 1,这很重要。这里使用mystring来保存流,字符串是通过‘\0’来判断流是在哪里结束的如果接收的字节把buf占满了,字符串复制到第511个还会继续下去,具体复制多少,这个不好说,反正在输出字符串时将会看到一堆,在每次复制完对buf重新填0同理以上。
这里需要先解析第一个包,获取响应头的正文长度,然后死循环读取,每次读到的字节数可能不太一样,具体参照滑动窗口(没记错的话,书上的东西早忘了)。
当然这种只适用于响应头里有正文长度的,如果是Transfer-Encoding: chunked那就是另外一回事了,这个过两天再写。
从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换

如何取名是个问题

给变量,函数。。。取名是个很麻烦的问题,写代码3年往往是看哪个不舒服就马上改。
为此有个很好的决解办法,一些简单的需求可以使用静态函数,这样就可以少取一个变量名了就像这样

	MyHttp::Response resp = MyHttp::Request::StaticRequest(MyHttp::Get, "http://www.baidu.com");
	cout << resp.ParseFromSource() << endl;

Run it
从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换
resp接手调用staticRequest回来的字节流,调用ParseFromSource进行解析,返回响应体。从图中可以看到一个问题,中文乱码很明显这是网页编码的问题返回的字节流是utf8而c++是gbk

gbk-utf8转换

现在把这两个函数封装到MyString内
这个不是我写的,来源看注释

//转换到gbk 中文显示乱码调用这个
	MyString ToGbk()
	{
		//由blog.****.net/u012234115/article/details/83186386 改过来
		auto src_str = c_str();
		int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
		wchar_t* wszGBK = new wchar_t[len + 1];
		memset(wszGBK, 0, len * 2 + 2);
		MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len);
		len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
		char* szGBK = new char[len + 1];
		memset(szGBK, 0, len + 1);
		WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
		MyString result(szGBK);
		if (wszGBK) delete[] wszGBK;
		if (szGBK) delete[] szGBK;
		return result;
	}

	//转换到utf8
	MyString ToUtf8()
	{
		//由blog.****.net/u012234115/article/details/83186386 改过来
		auto src_str = c_str();
		int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
		wchar_t* wszGBK = new wchar_t[len + 1];
		memset(wszGBK, 0, len * 2 + 2);
		MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len);
		len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
		char* szGBK = new char[len + 1];
		memset(szGBK, 0, len + 1);
		WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
		MyString result(szGBK);
		if (wszGBK) delete[] wszGBK;
		if (szGBK) delete[] szGBK;
		return result;
	}

解析响应默认不进行转换,不是所有请求都需要
从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换
Try again
从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换
没问题,本地测试环境试试
从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换

Post测试

chunked还不支持
从socket开始实现服务器及Http请求类 [1] 请求url处理,响应读取,gbk-utf8转换

无耻的求个star GitHub