最近需求需要開發一款 HTTP ,然后由於先前接觸過Qt,就直接用Qt寫HTTP服務器了,也是為了當作練手,要不然是直接上HTTP框架的。
后端用C++ Qt框架 前端為了練手 當然是純生的 js html css
具體的HTTP 實現過程我就不累贅描述了,這個Http協議解析基本上大部分人都知道原理。
主要是記錄一下開發中遇到的各種問題。
首先最開始開發的時候,一路順風,我的設計模式是 層次 設計模式,一層層獨立互不相干互不干涉。嚴格的只管理好自己的所在層。
數據包是一層層往上傳輸,到達 Logic 層 指令處理完畢之后 返回要顯示的數據(比如HTML),於是再一層層往下返回,一層層加報頭;
是不是有點類似於 七層協議?

由於軟件本身只是后台界面使用,所以並沒有考慮到 線程池,直接使用多線程。
在制作過程中,最經常遇到的莫過於就是編碼問題,本身應該是一個很簡單的問題,但是有時候確實出現的次數比較多。雖然說解決也簡單。
首先我們統一編碼。內部程序和源代碼和html文件均為 UTF-8。
在開發到 60%,也就是在設計 身份識別的地方,我們想了一個辦法,為了保證其安全性,我們用了一直理論上我們沒有找到什么缺陷的方法:

根據一段時間的討論,在不考慮Cookie被盜取(Cookie是以會話的形式存在)的情況下,似乎沒有發現什么問題。
以下為 Cookie 生成算法:
QString Cookie::getRandCookie(QString & name, QString & pass){
QByteArray bb;
QString temp;
QString md5_pass;
QString randcookie;
bb = QCryptographicHash::hash(name.toUtf8(), QCryptographicHash::Md5);
temp = bb.toHex();
bb = QCryptographicHash::hash(temp.toUtf8(), QCryptographicHash::Md5);
randcookie = bb.toHex();
bb = QCryptographicHash::hash(pass.toUtf8(), QCryptographicHash::Md5);
temp = bb.toHex();
bb = QCryptographicHash::hash(temp.toUtf8(), QCryptographicHash::Md5);
md5_pass = bb.toHex();
QTime t;
t = QTime::currentTime();
qsrand(t.msec() + t.second() * 65535 / 2);
int n = qrand();
QString tmp = QString::number(n, 1000);
bb = QCryptographicHash::hash(tmp.toUtf8(), QCryptographicHash::Md5);
tmp = bb.toHex();
randcookie = randcookie + tmp + md5_pass;
//目前總共有 32 位,為了防止用戶Cookie被XSS以免 碰撞機碰撞。 所以將返回不完整的Md5
// 6~28位是 用戶名 33位~64位是隨機數無用
QString userhead = randcookie.mid(6, 28); //取用戶名前面這一段 不完整
QString rand = randcookie.mid(33,97); //隨機數
QString passhead = randcookie.mid(72, 20); //這里去掉前8位 //取中間這一段 不給完整的 MD5碼
randcookie = rand+ userhead + passhead;//隨機數 賬號 密碼
randcookie += "_Hi_hacker"; //向大牛打聲招呼
return randcookie;
}
如果要是再看的你發現了漏洞,一定要留言告訴我,我將立即改進。
隨后我們遇到了編碼問題。
一個問題就是,因為我們的需求包括了 網頁操作控制台(用匿名管道實現,詳情可以看我的這篇文章:http://www.cnblogs.com/suwings/p/5754943.html,順帶一提,Qt框架也可以實現,只是由於時間問題,沒法再做描述)
但是Windows 控制台默認是 GBK 編碼,這將導致一個問題的出現,中文輸入的數據可能將亂碼。輸出的數據也可能將亂碼。
不過在我們測試的發現居然忘記URL中文解碼了,但是Qt自帶的解碼有個問題就是 英文有時候也會一起解碼。
於是在網上找到了如下實現方法:
1 /************************************************************************/
2 /* URL解碼 英文可不解 */
3 /************************************************************************/
4 std::string urlDecode(const std::string & _szToDecode)
5 {
6 std::string result;
7 int hex = 0;
8 for (size_t i = 0; i < _szToDecode.length(); ++i)
9 {
10 switch (_szToDecode[i])
11 {
12 case '+':
13 result += ' ';
14 break;
15 case '%':
16 if (isxdigit(_szToDecode[i + 1]) && isxdigit(_szToDecode[i + 2]))
17 {
18 std::string hexStr = _szToDecode.substr(i + 1, 2);
19 hex = strtol(hexStr.c_str(), 0, 16);
20 //字母和數字[0-9a-zA-Z]、一些特殊符號[$-_.+!*'(),] 、以及某些保留字[$&+,/:;=?@]
21 //可以不經過編碼直接用於URL
22 if (!((hex >= 48 && hex <= 57) || //0-9
23 (hex >= 97 && hex <= 122) || //a-z
24 (hex >= 65 && hex <= 90) || //A-Z
25 hex == 0x21 || hex == 0x24 || hex == 0x26 || hex == 0x27 || hex == 0x28 || hex == 0x29
26 || hex == 0x2a || hex == 0x2b || hex == 0x2c || hex == 0x2d || hex == 0x2e || hex == 0x2f
27 || hex == 0x3A || hex == 0x3B || hex == 0x3D || hex == 0x3f || hex == 0x40 || hex == 0x5f
28 ////一些特殊符號及保留字[$-_.+!*'(),] [$&+,/:;=?@]
29 ))
30 {
31 result += char(hex);
32 i += 2;
33 }else if{esult += '%';}else{result += '%';}
34 break;
35 default:
36 result += _szToDecode[i];
37 break;
38 }
39 }
40 return result;
41 }
於是很開心的完成了URL解碼,開始專注 到控制台的編碼問題:
從 QString UTF-8 轉到 Windows cmd GBK:
1 string UTF8ToGBK(const char* strUTF8)
2 {
3 int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0);
4 wchar_t* wszGBK = new wchar_t[len + 1];
5 memset(wszGBK, 0, len * 2 + 2);
6 MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len);
7 len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
8 char* szGBK = new char[len + 1];
9 memset(szGBK, 0, len + 1);
10 WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
11 string strTemp(szGBK);
12 if (wszGBK) delete[] wszGBK;
13 if (szGBK) delete[] szGBK;
14 return strTemp;
15 }
以及輸出: 從 Windows CMD GBK 轉回 UTF-8:
1 void Pipe::loop(){
2 char outbuff[4096]; //輸出緩沖
3 DWORD byteread;
4 while (true)
5 {
6 memset(outbuff, '\0', 4096);
7 if (ReadFile(this->hpiperead, outbuff, 4095, &byteread, NULL) == NULL)break;
8
9 QTextCodec *gbk1 = QTextCodec::codecForName("GBK"); //Windows 默認編碼 GBK 轉成 UTF-8 //主要是看這里
10 QString tmp = gbk1->toUnicode(outbuff); //主要是看這里
11 while(tmp.indexOf('\b') != -1)tmp.replace('\b',""); //替換管道可能出現的亂碼
12 //這樣 Qstring tmp 就可以使用了。
13 memset(outbuff, '\0', 4096);
14 }
15 }
然后差不多幾個重點的問題解決了。於是繼續愉快的code
但是好景不長,后來發現返回的數據在 HTTP 響應頭里面總是 少了,也就是說 四個漢字 “啊啊啊啊” 變成了 “啊啊”;
我原先一直以為是編碼問題,在TCP層我多層換編碼輸出,用UTF-8,GBK gb等等一些編碼。都無果。
后來發現 是一句代碼坑了這里:
//------處理數據長度--------
QString tmp_read_len;
//int i; 用前面用過的i,無需要重新那個
//Body 是QList類型
i = 0;
for (line = 0; line < body.size(); line++) //這個循環是 循環加入 從上層返回的數據
{
i = i + body[line].toLocal8Bit().length();
//字節數,判斷中文/英文 就是因為少了toLocal8Bit,所以導致中文判斷也以為是一個,實際上可能是 2 個或 4個 (UTF-8)
}
QString tmp_int_len = QString::number(i);
QString ContentLen = "Content-Length: " + tmp_int_len; //加入 Content-Length
(*list) << ContentLen;
於是解決之后,網頁終於能顯示“啊啊啊啊”了,於是又開始愉快的code。
可惜好景不長,在一個及其簡單的地方,出現了差錯。我需要實現一個 在控制台也可以操作的需求,這個很簡單,用一個線程專門讀取用戶輸入就好了
對...是很簡單
1 //循環等待輸入
2 void LoopCin(){
3 std::string com;
4 while (true){
5 char ch = '\0';
6 ch = getchar();
7 if (ch == '\n'){
8 //考慮多一點
9 if (PIPE != NULL){ //PIPE 是管道
10 std::cout << ">>" << com.c_str()<< std::endl;
11 PIPE->sendCommand(com.c_str()); //向管道發送命令 管道已經是封裝好了的
12 com = "";
13 }else{
14 std::cout << "[程序]" << "服務器未開啟,無法執行命令.請去網頁后台開啟您的服務器."<< std::endl;
15 }
16 }
17 else
18 {
19 com = com + ch; //如果不是回車 就加入char
20 }
21 }
22 }
於是寫完這些代碼之后,用C++11 的Thread 類創建線程(別問我為什么不用QThread 類,為了實現一個這個還用着那個,而且據說這個使用起來需要謹慎)
可是 用Thread創建的線程卻 毫無效果,明明可以等待用戶輸入了,卻將主線程個阻塞了,很是詫異。。
我也不是專門走C++這條路的,所以沒有詳細的去調查為什么,於是我用代替方法,直接使用了 WIndows API 創建線程。
1 PIPE_cin_hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)LoopCin, NULL, 0, &PIPE_cin_ThreadID);//創建輸入循環線程
奇跡般的不知道為什么的突然就可以了。莫非 Thread 創建的線程不可靠?不是沒有啟動,確實啟動了,但是卻阻塞了主線程,整個進程在等待我輸入,網頁也加載不出來了。
如果你知道這個原理,還煩請告訴我一下,謝謝。
於是又開始愉快的進行code。
雖然后面還有點小插曲,但是都一一解決,完成了這個項目。關於Javascript 編寫那里遇到的坑其實也沒多少,就不寫了。
不論是否對你有幫助,謝謝你的耐心閱讀

