Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Implement a 12306 ticket-grabbing software from scratch

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)06/03 Report--

< 0) { LogError("invalid station_name.js file"); fclose(pfile); } fseek(pfile, 0, SEEK_SET); length++; char* buf = new char[length]; memset(buf, 0, length*sizeof(char)); if (fread(buf, length-1, 1, pfile) != 1) { LogError("read station_name.js file error"); fclose(pfile); return false; } strResponse = buf; fclose(pfile); } /* 返回结果为一个js文件, var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2" */ //LogInfo("recv json = %s", strResponse.c_str()); OutputDebugStringA(strResponse.c_str()); vector singleStation; split(strResponse, "@", singleStation); size_t size = singleStation.size(); for (size_t i = 1; i < size; ++i) { vector v; split(singleStation[i], "|", v); if (v.size() < 6) continue; stationinfo st; st.code1 = v[0]; st.hanzi = v[1]; st.code2 = v[2]; st.pingyin = v[3]; st.simplepingyin = v[4]; st.no = atol(v[5].c_str()); si.push_back(st); } return true;} 这里用了一个站点信息结构体stationinfo,定义如下: //var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0@bjd|北京东|BOP|beijingdong|bjd|1@bji|北京|BJP|beijing|bj|2struct stationinfo{ string code1; string hanzi; string code2; string pingyin; string simplepingyin; int no;}; 因为我们这里目的是为了模拟http请求做买火车票相关的操作,而不是技术方面本身,所以为了快速实现我们的目的,我们就使用curl库。这个库是一个强大的http相关的库,例如12306服务器返回的数据可能是分块的(chunked),这个库也能帮我们组装好;再例如,服务器返回的数据是使用gzip格式压缩的,curl也会帮我们自动解压好。所以,接下来的所有12306的接口,都基于我封装的curl库一个接口: /** * 发送一个http请求 *@param url 请求的url *@param strResponse http响应结果 *@param get true为GET,false为POST *@param headers 附带发送的http头信息 *@param postdata post附带的数据 *@param bReserveHeaders http响应结果是否保留头部信息 *@param timeout http请求超时时间 */ bool HttpRequest(const char* url, string& strResponse, bool get = true, const char* headers = NULL, const char* postdata = NULL, bool bReserveHeaders = false, int timeout = 10); 函数各种参数已经在函数注释中写的清清楚楚了,这里就不一一解释了。这个函数的实现代码如下: bool Client12306::HttpRequest(const char* url, string& strResponse, bool get/* = true*/, const char* headers/* = NULL*/, const char* postdata/* = NULL*/, bool bReserveHeaders/* = false*/, int timeout/* = 10*/){ CURLcode res; CURL* curl = curl_easy_init(); if (NULL == curl) { LogError("curl lib init error"); return false; } curl_easy_setopt(curl, CURLOPT_URL, url); //响应结果中保留头部信息 if (bReserveHeaders) curl_easy_setopt(curl, CURLOPT_HEADER, 1); curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //设定为不验证证书和HOST curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); //设置超时时间 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout); curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(curl, CURLOPT_REFERER, URL_REFERER); //12306早期版本是不需要USERAGENT这个字段的,现在必须了,估计是为了避免一些第三方的非法刺探吧。 //如果没有这个字段,会返回 /* HTTP/1.0 302 Moved Temporarily Location: http://www.12306.cn/mormhweb/logFiles/error.html Server: Cdn Cache Server V2.0 Mime-Version: 1.0 Date: Fri, 18 May 2018 02:52:05 GMT Content-Type: text/html Content-Length: 0 Expires: Fri, 18 May 2018 02:52:05 GMT X-Via: 1.0 PSshgqdxxx63:10 (Cdn Cache Server V2.0) Connection: keep-alive X-Dscp-Value: 0 */ curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36"); //不设置接收的编码格式或者设置为空,libcurl会自动解压压缩的格式,如gzip //curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip, deflate, br"); //添加自定义头信息 if (headers != NULL) { //LogInfo("http custom header: %s", headers); struct curl_slist *chunk = NULL; chunk = curl_slist_append(chunk, headers); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); } if (!get && postdata != NULL) { //LogInfo("http post data: %s", postdata); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata); } LogInfo("http %s: url=%s, headers=%s, postdata=%s", get ? "get" : "post", url, headers != NULL ? headers : "", postdata!=NULL?postdata : ""); res = curl_easy_perform(curl); bool bError = false; if (res == CURLE_OK) { int code; res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); if (code != 200 && code != 302) { bError = true; LogError("http response code is not 200 or 302, code=%d", code); } } else { LogError("http request error, error code = %d", res); bError = true; } curl_easy_cleanup(curl); LogInfo("http response: %s", strResponse.c_str()); return !bError;} 正如上面注释中所提到的,浏览器在发送http请求时带的一些字段,我们不是必须的,如查票接口浏览器可能会发以下http数据包: GET /otn/leftTicket/query?leftTicketDTO.train_date=2018-05-30&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=BJP&purpose_codes=ADULT HTTP/1.1Host: kyfw.12306.cnConnection: keep-aliveCache-Control: no-cacheAccept: */*X-Requested-With: XMLHttpRequestIf-Modified-Since: 0User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36Referer: https://kyfw.12306.cn/otn/leftTicket/initAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Cookie: JSESSIONID=ACD9CB098169C4D73CDE80D6F6C38E5A; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _jc_save_fromDate=2018-05-30; _jc_save_toDate=2018-05-20 其中像Connection、Cache-Control、Accept、If-Modified-Since等字段都不是必须的,所以我们在模拟我们自己的http请求时可以不用可以添加这些字段,当然据我观察,12306服务器现在对发送过来的http数据包要求越来越严格了,如去年的时候,User-Agent这个字段还不是必须的,现在如果你不带上这个字段,可能12306返回的结果就不一定正确。当然,不正确的结果中一定不会有明确的错误信息,充其量可能会告诉你页面不存在或者系统繁忙请稍后再试,这是服务器自我保护的一种重要的措施,试想你做服务器程序,会告诉非法用户明确的错误信息吗?那样不就给了非法×××服务器的人不断重试的机会了嘛。 需要特别注意的是:查票接口发送的http协议的头还有一个字段叫Cookie,其值是一串非常奇怪的东西:JSESSIONID=ACD9CB098169C4D73CDE80D6F6C38E5A; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _jc_save_fromDate=2018-05-30; _jc_save_toDate=2018-05-2。 在这串字符中有一个JSESSIONID,在不需要登录的查票接口,我们可以传或者不传这个字段值。但是在购票以及查询常用联系人这些需要在已经登录的情况下才能进行的操作,我们必须带上这个数据,这是服务器给你的token(验证令牌),而这个令牌是在刚进入12306站点时,服务器发过来的,你后面的登录等操作必须带上这个token,否则服务器会认为您的请求是非法请求。我第一次去研究12306的买票流程时,即使在用户名、密码和图片验证码正确的情况下,也无法登录就是这个原因。这是12306为了防止非法登录使用的一个安全措施。 二、登录与拉取图片验证码接口 我的登录页面效果如下: 12306的图片验证码一般由八个图片组成,像上面的"龙舟"文字,也是图片,这两处的图片(文字图片和验证码)都是在服务器上拼装后,发给客户端的,12306服务器上这种类型的小图片有一定的数量,虽然数量比较大,但是是有限的。如果你要做验证码自动识别功能,可以尝试着下载大部分图片,然后做统计规律。所以,我这里并没有做图片自动识别功能。有兴趣的读者可自行尝试。 先说下,拉取验证码的接口。我们打开Chrome浏览器12306的登录界面:https://kyfw.12306.cn/otn/login/init,如下图所示: 可以得到拉取验证码的接口: 我们可以看到发送的http请求数据包格式是: GET /passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.7520968747611347 HTTP/1.1Host: kyfw.12306.cnConnection: keep-aliveUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36Accept: image/webp,image/apng,image/*,*/*;q=0.8Referer: https://kyfw.12306.cn/otn/login/initAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Cookie: _passport_session=badc97f6a852499297796ee852515f957153; _passport_ct=9cf4ea17c0dc47b6980cac161483f522t9022; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _jc_save_fromDate=2018-05-30; _jc_save_toDate=2018-05-20; BIGipServerpassport=837288202.50215.0000 这里也是一个http GET请求,Host、Referer和Cookie这三个字段是必须的,且Cookie字段必须带上上文说的JSESSIONID,下载图片验证码和下文中各个步骤也必须在Cookie字段中带上这个JSESSIONID值,否则无法从12306服务器得到正确的应答。后面会介绍如何拿到这个这。这个拉取图片验证码的http GET请求需要三个参数,如上面的代码段所示,即login_site、module、rand和一个类似于0.7520968747611347的随机值,前三个字段的值都是固定的,module字段表示当前是哪个模块,当前是登录模块,所以值是login,后面获取最近联系人时取值是passenger。这里还有一个需要注意的地方是,如果您验证图片验证码失败时,重新请求图片时,必须也重新请求下JSESSIONID。这个url是https://kyfw.12306.cn/otn/login/init。http请求和应答包如下: 请求包: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Cache-Control: max-age=0Connection: keep-aliveCookie: RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _jc_save_fromStation=%u4E0A%u6D77%2CSHH; _jc_save_toStation=%u5317%u4EAC%2CBJP; _jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _jc_save_fromDate=2018-05-30; _jc_save_toDate=2018-05-20; BIGipServerpassport=837288202.50215.0000Host: kyfw.12306.cnReferer: https://kyfw.12306.cn/otn/passport?redirect=/otn/login/loginOutUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 应答包: HTTP/1.1 200 OKDate: Sun, 20 May 2018 02:23:53 GMTContent-Type: text/html;charset=utf-8Transfer-Encoding: chunkedSet-Cookie: JSESSIONID=D5AE154D66F67DE53BF70420C772158F; Path=/otnct: C1_217_101_6Content-Language: zh-CNContent-Encoding: gzipX-Via: 1.1 houdianxin184:4 (Cdn Cache Server V2.0)Connection: keep-aliveX-Dscp-Value: 0X-Cdn-Src-Port: 46480 这个值在应答包字段Set-Cookie中拿到: Set-Cookie: JSESSIONID=D5AE154D66F67DE53BF70420C772158F; Path=/otn 所以,我们每次请求图片验证码时,都重新请求一下这个JSESSIONID,代码如下: #define URL_LOGIN_INIT "https://kyfw.12306.cn/otn/login/init"bool Client12306::loginInit(){ string strResponse; if (!HttpRequest(URL_LOGIN_INIT, strResponse, true, "Upgrade-Insecure-Requests: 1", NULL, true, 10)) { LogError("loginInit failed"); return false; } if (!GetCookies(strResponse)) { LogError("parse login init cookie error, url=%s", URL_LOGIN_INIT); return false; } return true;}bool Client12306::GetCookies(const string& data){ if (data.empty()) { LogError("http data is empty"); return false; } //解析http头部 string str; str.append(data.c_str(), data.length()); size_t n = str.find("\r\n\r\n"); string header = str.substr(0, n); str.erase(0, n + 4); //m_cookie.clear(); //获取http头中的JSESSIONID=21AC68643BBE893FBDF3DA9BCF654E98; vector v; while (true) { size_t index = header.find("\r\n"); if (index == string::npos) break; string tmp = header.substr(0, index); v.push_back(tmp); header.erase(0, index + 2); if (header.empty()) break; } string jsessionid; string BIGipServerotn; string BIGipServerportal; string current_captcha_type; size_t m; OutputDebugStringA("\nresponse http headers:\n"); for (size_t i = 0; i < v.size(); ++i) { OutputDebugStringA(v[i].c_str()); OutputDebugStringA("\n"); m = v[i].find("Set-Cookie: "); if (m == string::npos) continue; string tmp = v[i].substr(11); Trim(tmp); m = tmp.find("JSESSIONID"); if (m != string::npos) { size_t comma = tmp.find(";"); if (comma != string::npos) jsessionid = tmp.substr(0, comma); } m = tmp.find("BIGipServerotn"); if (m != string::npos) { size_t comma = tmp.find(";"); if (comma != string::npos) BIGipServerotn = tmp.substr(m, comma); else BIGipServerotn = tmp; } m = tmp.find("BIGipServerportal"); if (m != string::npos) { size_t comma = tmp.find(";"); if (comma != string::npos) BIGipServerportal = tmp.substr(m, comma); else BIGipServerportal = tmp; } m = tmp.find("current_captcha_type"); if (m != string::npos) { size_t comma = tmp.find(";"); if (comma != string::npos) current_captcha_type = tmp.substr(m, comma); else current_captcha_type = tmp; } } if (!jsessionid.empty()) { m_strCookies = jsessionid; m_strCookies += "; "; m_strCookies += BIGipServerotn; if (!BIGipServerportal.empty()) { m_strCookies += "; "; m_strCookies += BIGipServerportal; } m_strCookies += "; "; m_strCookies += current_captcha_type; return true; } LogError("jsessionid is empty"); return false;}#define URL_GETPASSCODENEW "https://kyfw.12306.cn/passport/captcha/captcha-image"bool Client12306::DownloadVCodeImage(const char* module){ if (module == NULL) { LogError("module is invalid"); return false; } //https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&0.06851784300754482 ostringstream osUrl; osUrl tm_sec);#else sprintf(m_szCurrVCodeName, "vcodedddddd.v", 1900 + tblock->

Tm_year, 1 + tblock- > tm_mon, tblock- > tm_mday, tblock- > tm_hour, tblock- > tm_min, tblock- > tm_sec); # endif FILE* fp = fopen (m_szCurrVCodeName, "wb"); if (fp = = NULL) {LogError ("open file% s error", m_szCurrVCodeName); return false;} const char* p = strResponse.data () Size_t count = fwrite (p, strResponse.length (), 1, fp); if (count! = 1) {LogError ("write file% s error", m_szCurrVCodeName); fclose (fp); return false;} fclose (fp); return true;}

Let's take a look at the interface https://kyfw.12306.cn/passport/captcha/captcha-check where the CAPTCHA goes to the server for verification.

Request header:

POST / passport/captcha/captcha-check HTTP/1.1Host: kyfw.12306.cnConnection: keep-aliveContent-Length: 50Accept: application/json, text/javascript, * / *; q=0.01Origin: https://kyfw.12306.cnX-Requested-With: XMLHttpRequestUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36Content-Type: application/x-www-form-urlencoded Charset=UTF-8Referer: https://kyfw.12306.cn/otn/login/initAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Cookie: _ passport_session=3e39a33a25bf4ea79146bd9362c11ad62327; _ passport_ct=c5c7940e08ce44db9ad05d213c1296ddt4410; RAIL_EXPIRATION=1526978933395; RAIL_DEVICEID=WKxIYg-q1zjIPVu7VjulZ9PqEGvW2gUB9LvoM1Vx8fa7l3SUwnO_BVSatbTq506c6VYNOaxAiRaUcGFTMjCz9cPayEIc9vJ0pHaXdSqDlujJP8YrIoXbpAAs60l99z8bEtnHgAJzxLzKiv2nka5nmLY_BMNur8b8; _ jc_save_fromStation=%u4E0A%u6D77%2CSHH; _ jc_save_toStation=%u5317%u4EAC%2CBJP; _ jc_save_wfdc_flag=dc; route=c5c62a339e7744272a54643b3be5bf64; BIGipServerotn=1708720394.50210.0000; _ jc_save_fromDate=2018-05-30 _ jc_save_toDate=2018-05-20; BIGipServerpassport=837288202.50215.0000

This is a POST request, where the coordinates X and Y selected by the input image CAPTCHA on the POST data:

Answer: 175,58,30,51 login_site: E rand: sjrand

Here I chose two pictures, so there are two sets of coordinate values, (175558) is one group, and (30551) is the other group, this coordinate system is as follows:

Because the size of each picture is the same, I can set a coordinate range for each picture, when I select a picture, give a coordinate in it, not necessarily the exact location when the mouse is clicked:

/ / Refresh verification code. Pass "randp" to the verification code in login status, and "sjrand" to non-login. For more information, please see the parameter struct VCodePosition {int x; int y;} in the original otsweb. Const VCodePosition g_pos [] = {{39,40}, {114,43}, {186,42}, {252,47}, {36,120}, {115,125}, {194,125}, {256,120}}; / / position of the eight blocks of the CAPTCHA image struct VCODE_SLICE_POS {int xLeft; int xRight; int yTop; int yBottom;} Const VCODE_SLICE_POS g_VCodeSlicePos [] = {{0,70,0,70}, {71,140,0,70}, {141,210,0,70}, {211,280,0,70}, {0,70,140}, {71,140,70,140}, {141,210,70,140}, {211,280,70,140}} / / Mouse click status of 8 CAPTCHA blocks bool g_bVodeSlice1Pressed [8] = {false, false}

The interface code of the verified image verification code is:

Int Client12306::checkRandCodeAnsyn (const char* vcode) {string param; param = "randCode="; param + = vcode; param + = "& rand=sjrand"; / / passenger:randp string strResponse; string strCookie = "Cookie:"; strCookie + = Cookie Cookies; if (! HttpRequest (URL_CHECKRANDCODEANSYN, strResponse, false, strCookie.c_str (), param.c_str (), false, 10) {LogError ("checkRandCodeAnsyn failed"); return-1 } / / * * successfully returned / / HTTP/1.1 200OK / / Date: Thu, 05 Jan 2017 07:44:16 GMT / / Server: Apache-Coyote/1.1 / / X-Powered-By: Servlet 2.5; JBoss-5.0/JBossWeb-2.1 / / ct: c1room103 / / Content-Type: application/json Charset=UTF-8 / / Content-Length: 144144/ X-Via: 1.1jiandianxin29:6 (Cdn Cache Server V2.0) / / Connection: keep-alive / / X-Cdn-Src-Port: 19153 / / invalid parameter / / {"validateMessagesShowId": "_ validatorMessage", "status": true, "httpstatus": 200," data ": {" result ":" 0 "," msg ":"}, "messages": [] "validateMessages": {}} / / CAPTCHA expires / / {"validateMessagesShowId": "_ validatorMessage", "status": true, "httpstatus": 200," data ": {" result ":" 0 "," msg ":" EXPIRED "}," messages ": []," validateMessages ": {}} / / CAPTCHA error / / {" validateMessagesShowId ":" _ validatorMessage "," status ": true," httpstatus ": 200," data ": {" result ":" 1 " "msg": "FALSE"}, "messages": [], "validateMessages": {}} / / the verification code is correct / / {"validateMessagesShowId": "_ validatorMessage", "status": true, "httpstatus": 200," data ": {" result ":" 1 "," msg ":" TRUE "}," messages ": []," validateMessages ": {} Json::Reader JsonReader Json::Value JsonRoot; if (! JsonReader.parse (strResponse, JsonRoot)) return-1 / / {"validateMessagesShowId": "_ validatorMessage", "status": true, "httpstatus": 200,200 "data": {"result": "1", "msg": "TRUE"}, "messages": [], "validateMessages": {}} if (JsonRoot ["status"]. IsNull () | JsonRoot ["status"]. AsBool ()! = true) return-1 If (JsonRoot ["httpstatus"]. IsNull () | | JsonRoot ["httpstatus"]. AsInt ()! = 200) return-1; if (JsonRoot ["data"]. IsNull () | |! JsonRoot ["data"]. IsObject () return-1; if (JsonRoot ["data"] ["result"]. IsNull ()) return-1 If (JsonRoot ["data"] ["result"] .asString ()! = "1" & & JsonRoot ["data"] ["result"] .asString ()! = "0") return-1; if (JsonRoot ["data"] ["msg"]. IsNull () return-1; / / if (JsonRoot ["data"] ["msg"]. AsString (). Empty ()) / return-1 If (JsonRoot ["data"] ["msg"]. AsString () = "") return 0; else if (JsonRoot ["data"] ["msg"]. AsString () = = "FALSE") return 1; return 1;}

Similarly, the implementation code for the interface to verify the user name and password is also given here:

Int Client12306::loginAysnSuggest (const char* user, const char* pass, const char* vcode) {string param = "loginUserDTO.user_name="; param + = user; param + = "& userDTO.password="; param + = pass; param + = "& randCode="; param + = vcode; string strResponse; string strCookie = "Cookie:"; strCookie + = m_strCookies If (! HttpRequest (URL_LOGINAYSNSUGGEST, strResponse, false, strCookie.c_str (), param.c_str (), false, 10) {LogError ("loginAysnSuggest failed"); return 2;} / / * * successfully returned / / HTTP/1.1 200 OK / / Date: Thu, 05 Jan 2017 07:49:53 GMT / / Server: Apache-Coyote/1.1 / / X-Powered-By: Servlet 2.5 JBoss-5.0/JBossWeb-2.1 / / ct: c1_103 / / Content-Type: application/json Charset=UTF-8 / / Content-Length: 146146X-Via: 1.1f186 Cdn Cache Server V2.0 / / Connection: keep-alive / / X-Cdn-Src-Port: 48361 / / mailbox does not exist / / {"validateMessagesShowId": "_ validatorMessage", "status": true, "httpstatus": 200," data ": {}," messages ": [" the mailbox does not exist. "] , "validateMessages": {}} / / password error / / {"validateMessagesShowId": "_ validatorMessage", "status": true, "httpstatus": 200," data ": {}," messages ": [" password error. If you enter more than 4 errors, the user will be locked. "] , "validateMessages": {}} / / login succeeded / / {"validateMessagesShowId": "_ validatorMessage", "status": true, "httpstatus": 200," data ": {" otherMsg ":", loginCheck: "Y"}, "messages": [], "validateMessages": {}} / / WCHAR* psz1 = Utf8ToAnsi (strResponse.c_str ()); / / wstring str = psz1; / / delete [] psz1; Json::Reader JsonReader; Json::Value JsonRoot If (! JsonReader.parse (strResponse, JsonRoot)) return 2; / / {"validateMessagesShowId": "_ validatorMessage", "status": true, / / "httpstatus": 200, "data": {"otherMsg": "", loginCheck: "Y"}, "messages": [], "validateMessages": {}} if (JsonRoot ["status"]. IsNull () return-1 Bool bStatus = JsonRoot ["status"] .asBool (); if (! bStatus) return-1; if (JsonRoot ["httpstatus"] .isNull () | | JsonRoot ["httpstatus"]. AsInt ()! = 200) return 2; if (JsonRoot ["data"]. IsNull () |! JsonRoot ["data"]. IsObject () return 2 If (JsonRoot ["data"] ["otherMsg"] .isNull () | | JsonRoot ["data"] ["otherMsg"] .asString ()! = "") return 2; if (JsonRoot ["data"] ["loginCheck"] .isNull () | JsonRoot ["data"] ["loginCheck"]. AsString ()! = "Y") return 1; return 0;}

There is also a detail to pay attention to, that is, the data sent through POST requests need to URL Encode some symbols, which I described in detail in my previous article, "implementing a http server from scratch". If you don't know, you can see the previous article. Therefore, the comma information contained in the coordinate information of the CAPTCHA to the picture should be encoded by URL, from

Answer=114,54,44,46&login_site=E&rand=sjrand

Become

Answer=114%2C54%2C44%2C46&login_site=E&rand=sjrand

Therefore, the value of the Content-Length field specified in the http header should be the encoded string length, not the original length, which is particularly error-prone.

If the verification is successful, the next step is to check and purchase tickets. Here will not be introduced one by one, all the principles are the same, the author can explore their own. Of course, I have explored all the interfaces and implemented them. Let me post them here:

/ * * @ desc: encapsulates the class that acquires 12306 requests such as verification code, verification code, login and so on. Client12306.h file * @ author: zhangyl * @ date: 2017.01.17 * / # ifndef _ CLIENT_12306_H__#define _ CLIENT_12306_H__#include # include using namespace std;// train type # define TRAIN_GC 0x00000001#define TRAIN_D (0x00000001

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report