【背景】
折腾:
期间,需要去发送POST操作,去模拟登陆百度,且post时要传递对应的post数据。
【折腾过程】
1.自己参考官网:
http://golang.org/pkg/net/http/
去看看,POST的参数和NewRequest的参数。
2.期间想要实现go中的函数的可选参数,默认参数,结果不支持:
3.后来看了半天,很多帖子,貌似都是那个Form或PostForm的,但是感觉不太对。
4.参考:
看到是http.NewRequest设置为POST时,还传递了对应的postData了。
然后就去看看:
bytes.NewReader
的输入参数,是什么类型的。
http://golang.org/pkg/bytes/#NewReader
然后发现,就是普通的,byte[]
5.所以到目前为止,貌似明白了:
对于POST时所要传递的post data
是需要:
在http.NewRequest
http://golang.org/pkg/net/http/#NewRequest
时传入给:
body io.Reader |
的,
但是:
http://golang.org/pkg/io/#Reader
不会用。
而别人传入的:
http://golang.org/pkg/bytes/#NewReader
还是基本能看懂的:
就是一个普通的reader,然后输入的是[]byte
而这个[]byte
是post data这string,被转换为对应的[]byte
而对应的post data的string,是key=value,中间通过&分隔开的。
需要自己,对于输入的postDict的键值对,自己组合出来。
或者是:
go中,或许也有对应的库函数去实现encode。
6.而之前看到:
http://golang.org/pkg/net/http/httputil/
看看有没有这样的工具。
不过貌似没看到。
但是看到其他的东西:
http://golang.org/pkg/net/http/httputil/#DumpRequest
可以帮你打印出来request,供调试用。
7.看到:
How to make an HTTP POST request in many languages
想到了:
go中,是不会用另外的url,去实现类似于
C#中的HttpUtility.UrlEncode?
所以去看看url:
http://golang.org/pkg/net/url/
然后终于找到所要的了:
http://golang.org/pkg/net/url/#Values.Encode
func (Values) Encodefunc (v Values) Encode() string Encode encodes the values into “URL encoded” form. e.g. "foo=bar&bar=baz" |
8.然后就可以去尝试写POST相关代码了。
然后是用下面核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | /* * [File] * EmulateLoginBaidu.go * * [Function] * 【记录】用go语言实现模拟登陆百度 * * [Version] * 2013-09-21 * * [Contact] */ package main import ( "fmt" "os" "runtime" "path" "strings" "time" //"io" "io/ioutil" "net/http" "net/http/cookiejar" "net/url" "bytes" ) /*************************************************************************************************** Global Variables ***************************************************************************************************/ var gCurCookies []*http.Cookie; var gCurCookieJar *cookiejar.Jar; var gLogger log4go.Logger; /*************************************************************************************************** Functions ***************************************************************************************************/ //do init before all others func initAll(){ gCurCookies = nil //var err error; gCurCookieJar,_ = cookiejar.New(nil) gLogger = nil //...... } //get url response html func getUrlRespHtml(strUrl string, postDict map[string]string) string{ gLogger.Info( "getUrlRespHtml, strUrl=%s" , strUrl) gLogger.Info( "postDict=%s" , postDict) var respHtml string = "" ; httpClient := &http.Client{ //Transport:nil, //CheckRedirect: nil, Jar:gCurCookieJar, } var httpReq *http.Request //var newReqErr error if nil == postDict { gLogger.Info( "is GET" ) //httpReq, newReqErr = http.NewRequest("GET", strUrl, nil) httpReq, _ = http.NewRequest( "GET" , strUrl, nil) // ... //httpReq.Header.Add("If-None-Match", `W/"wyzzy"`) } else { gLogger.Info( "is POST" ) postValues := url.Values{} for postKey, PostValue := range postDict{ postValues.Set(postKey, PostValue) } gLogger.Info( "postValues=%s" , postValues) postDataStr := postValues.Encode() gLogger.Info( "postDataStr=%s" , postDataStr) postDataBytes := [] byte (postDataStr) gLogger.Info( "postDataBytes=%s" , postDataBytes) postBytesReader := bytes.NewReader(postDataBytes) //httpReq, newReqErr = http.NewRequest("POST", strUrl, postBytesReader) httpReq, _ = http.NewRequest( "POST" , strUrl, postBytesReader) //httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") httpReq.Header.Add( "Content-Type" , "application/x-www-form-urlencoded" ) } httpResp, err := httpClient.Do(httpReq) // ... //httpResp, err := http.Get(strUrl) //gLogger.Info("http.Get done") if err != nil { gLogger.Warn( "http get strUrl=%s response error=%s\n" , strUrl, err.Error()) } gLogger.Info( "httpResp.Header=%s" , httpResp.Header) gLogger.Debug( "httpResp.Status=%s" , httpResp.Status) defer httpResp.Body.Close() // gLogger.Info("defer httpResp.Body.Close done") body, errReadAll := ioutil.ReadAll(httpResp.Body) //gLogger.Info("ioutil.ReadAll done") if errReadAll != nil { gLogger.Warn( "get response for strUrl=%s got error=%s\n" , strUrl, errReadAll.Error()) } //gLogger.Debug("body=%s\n", body) //gCurCookies = httpResp.Cookies() //gCurCookieJar = httpClient.Jar; gCurCookies = gCurCookieJar.Cookies(httpReq.URL); //gLogger.Info("httpResp.Cookies done") //respHtml = "just for test log ok or not" respHtml = string(body) //gLogger.Info("httpResp body []byte to string done") return respHtml } func dbgPrintCurCookies() { var cookieNum int = len(gCurCookies); gLogger.Info( "cookieNum=%d" , cookieNum) for i := 0 ; i < cookieNum; i++ { var curCk *http.Cookie = gCurCookies[i]; //gLogger.Info("curCk.Raw=%s", curCk.Raw) gLogger.Info( "------ Cookie [%d]------" , i) gLogger.Info( "Name\t\t=%s" , curCk.Name) gLogger.Info( "Value\t=%s" , curCk.Value) gLogger.Info( "Path\t\t=%s" , curCk.Path) gLogger.Info( "Domain\t=%s" , curCk.Domain) gLogger.Info( "Expires\t=%s" , curCk.Expires) gLogger.Info( "RawExpires\t=%s" , curCk.RawExpires) gLogger.Info( "MaxAge\t=%d" , curCk.MaxAge) gLogger.Info( "Secure\t=%t" , curCk.Secure) gLogger.Info( "HttpOnly\t=%t" , curCk.HttpOnly) gLogger.Info( "Raw\t\t=%s" , curCk.Raw) gLogger.Info( "Unparsed\t=%s" , curCk.Unparsed) } } func main() { initAll() //...... //step3: verify returned cookies if bGotCookieBaiduid && bExtractTokenValueOK { gLogger.Info( "======步骤3:登陆百度并检验返回的Cookie ======" ); postDict := map[string]string{} //postDict["ppui_logintime"] = "" postDict[ "charset" ] = "utf-8" //postDict["codestring"] = "" postDict[ "token" ] = strLoginToken postDict[ "isPhone" ] = "false" postDict[ "index" ] = "0" //postDict["u"] = "" //postDict["safeflg"] = "0" postDict[ "staticpage" ] = staticPageUrl postDict[ "loginType" ] = "1" postDict[ "tpl" ] = "mn" postDict[ "callback" ] = "parent.bdPass.api.login._postCallback" strBaiduUsername := "" strBaiduPassword := "" // stdinReader := bufio.NewReader(os.Stdin) // inputBytes, _ := stdinReader.ReadString('\n') // fmt.Printf("Input Char Is : %v", string([]byte(input)[0])) //_, err1 := fmt.Scanf("%s", &strBaiduUsername) //fmt.Println("Plese input:") //fmt.Println("Baidu Username:") gLogger.Info( "Plese input:" ) gLogger.Info( "Baidu Username:" ) _, err1 := fmt.Scanln(&strBaiduUsername) if nil == err1 { gLogger.Info( "strBaiduUsername=%s" , strBaiduUsername) } //fmt.Println("Baidu Password:") gLogger.Info( "Baidu Password:" ) //_, err2 := fmt.Scanf("%s", &strBaiduPassword) _, err2 := fmt.Scanln(&strBaiduPassword) if nil == err2 { gLogger.Info( "strBaiduPassword=%s" , strBaiduPassword) } postDict[ "username" ] = strBaiduUsername postDict[ "password" ] = strBaiduPassword postDict[ "verifycode" ] = "" postDict[ "mem_pass" ] = "on" gLogger.Debug( "postDict=%s" , postDict) loginBaiduRespHtml := getUrlRespHtml(baiduMainLoginUrl, postDict); gLogger.Debug( "loginBaiduRespHtml=%s" , loginBaiduRespHtml) } } |
实现了可以成功发送POST,传递进入post data:
1 2 3 4 5 6 7 8 9 10 11 12 13 | [2013/09/21 18:26:42 ] [INFO] (main.main:294) ======步骤3:登陆百度并检验返回的Cookie ====== [2013/09/21 18:26:42 ] [INFO] (main.main:319) Plese input: [2013/09/21 18:26:42 ] [INFO] (main.main:320) Baidu Username: [2013/09/21 18:26:48 ] [INFO] (main.main:323) strBaiduUsername=xxxxxx [2013/09/21 18:26:48 ] [INFO] (main.main:326) Baidu Password: [2013/09/21 18:26:50 ] [INFO] (main.main:330) strBaiduPassword=yyyyyy [2013/09/21 18:26:50 ] [DEBG] (main.main:338) postDict=map[charset:utf-8 isPhone:false index:0 tpl:mn username:xxxxxx verifycode: token:0933758100af2943e1948ea011386ac8 staticpage:http://www.baidu.com/cache/user/html/jump.html loginType:1 callback:parent.bdPass.api.login._postCallback password:yyyyyy mem_pass:on] [2013/09/21 18:26:50 ] [INFO] (main.getUrlRespHtml:127) getUrlRespHtml, strUrl=https://passport.baidu.com/v2/api/?login [2013/09/21 18:26:50 ] [INFO] (main.getUrlRespHtml:128) postDict=map[token:0933758100af2943e1948ea011386ac8 staticpage:http://www.baidu.com/cache/user/html/jump.html loginType:1 callback:parent.bdPass.api.login._postCallback password:yyyyyy mem_pass:on charset:utf-8 isPhone:false index:0 tpl:mn username:xxxxxx verifycode:] [2013/09/21 18:26:50 ] [INFO] (main.getUrlRespHtml:147) is POST [2013/09/21 18:26:50 ] [INFO] (main.getUrlRespHtml:152) postValues=map[token:[0933758100af2943e1948ea011386ac8] staticpage:[http://www.baidu.com/cache/user/html/jump.html] loginType:[1] callback:[parent.bdPass.api.login._postCallback] password:[yyyyyy] mem_pass:[on] charset:[utf-8] isPhone:[false] index:[0] tpl:[mn] username:[xxxxxx] verifycode:[]] [2013/09/21 18:26:50 ] [INFO] (main.getUrlRespHtml:154) postDataStr=callback=parent.bdPass.api.login._postCallback&charset=utf-8&index=0&isPhone=false&loginType=1&mem_pass=on&password=yyyyyy&staticpage=http%3A%2F%2Fwww.baidu.com%2Fcache%2Fuser%2Fhtml%2Fjump.html&token=0933758100af2943e1948ea011386ac8&tpl=mn&username=xxxxxx&verifycode= [2013/09/21 18:26:50 ] [INFO] (main.getUrlRespHtml:156) postDataBytes=callback=parent.bdPass.api.login._postCallback&charset=utf-8&index=0&isPhone=false&loginType=1&mem_pass=on&password=yyyyyy&staticpage=http%3A%2F%2Fwww.baidu.com%2Fcache%2Fuser%2Fhtml%2Fjump.html&token=0933758100af2943e1948ea011386ac8&tpl=mn&username=xxxxxx&verifycode= |
然后模拟登陆百度,可以正常返回对应的各种cookie的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [2013/09/21 18:26:50 ] [INFO] (main.getUrlRespHtml:172) httpResp.Header=map[Etag:[w/"Zv4yMMLJASqKOV17EWzXUpi3rNVEPvgZ:1379759203"] Set-Cookie:[BDUSS=G1LNG5uLTNYWkU2bzA2SGxCZHZ2Rm5ocnN-MEhFem5uQkZrdkJFVmplUmpBV1ZTQVFBQUFBJCQAAAAAAAAAAAEAAAB-OUgCYWdhaW5pbnB1dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGN0PVJjdD1SM; expires=Wed, 08-Dec-2021 10:26:43 GMT; path=/; domain=baidu.com; httponly PTOKEN=deleted; expires=Fri, 21-Sep-2012 10:26:42 GMT; path=/; domain=baidu.com; httponly PTOKEN=0f1e0187b042630a47c4eea8e0e96a2f; expires=Wed, 08-Dec-2021 10:26:43 GMT; path=/; domain=passport.baidu.com; httponly STOKEN=8d6ce0cbc7f689a8cd647b8beb5872e3; expires=Wed, 08-Dec-2021 10:26:43 GMT; path=/; domain=passport.baidu.com; httponly SAVEUSERID=deleted; expires=Fri, 21-Sep-2012 10:26:42 GMT; path=/; domain=passport.baidu.com; httponly USERNAMETYPE=1; expires=Wed, 08-Dec-2021 10:26:43 GMT; path=/; domain=passport.baidu.com; httponly] Cache-Control:[public] Server:[] P3p:[CP=" OTI DSP COR IVA OUR IND COM "] Date:[Sat, 21 Sep 2013 10:26:43 GMT] Content-Type:[text/html] Connection:[keep-alive] Last-Modified:[Sat, 21 Sep 2013 10:26:43 10SepGMT] Pragma:[public] Expires:[0] Vary:[Accept-Encoding]] [2013/09/21 18:26:50 ] [DEBG] (main.getUrlRespHtml:173) httpResp.Status=200 OK [2013/09/21 18:26:50 ] [DEBG] (main.main:342) loginBaiduRespHtml=<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <script type="text/javascript"> //parent.callback(url) window.location.replace(url); </script> </body> </html> |
如图:
至此,至少基本的POST,是搞定了。
【总结】
此处,对于http的POST,且要传递对应的post data的话,最最核心的代码是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | import ( "net/http" "net/url" "bytes" ) //get url response html func getUrlRespHtml(strUrl string, postDict map[string]string) string{ //...... httpClient := &http.Client{ //Transport:nil, //CheckRedirect: nil, Jar:gCurCookieJar, } var httpReq *http.Request //var newReqErr error if nil == postDict { gLogger.Info( "is GET" ) //httpReq, newReqErr = http.NewRequest("GET", strUrl, nil) httpReq, _ = http.NewRequest( "GET" , strUrl, nil) // ... //httpReq.Header.Add("If-None-Match", `W/"wyzzy"`) } else { gLogger.Info( "is POST" ) postValues := url.Values{} for postKey, PostValue := range postDict{ postValues.Set(postKey, PostValue) } gLogger.Info( "postValues=%s" , postValues) postDataStr := postValues.Encode() gLogger.Info( "postDataStr=%s" , postDataStr) postDataBytes := [] byte (postDataStr) gLogger.Info( "postDataBytes=%s" , postDataBytes) postBytesReader := bytes.NewReader(postDataBytes) //httpReq, newReqErr = http.NewRequest("POST", strUrl, postBytesReader) httpReq, _ = http.NewRequest( "POST" , strUrl, postBytesReader) //httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") httpReq.Header.Add( "Content-Type" , "application/x-www-form-urlencoded" ) } httpResp, err := httpClient.Do(httpReq) //...... } func main() { //...... postDict := map[string]string{} postDict[ "charset" ] = "utf-8" postDict[ "token" ] = strLoginToken postDict[ "isPhone" ] = "false" postDict[ "index" ] = "0" //...... loginBaiduRespHtml := getUrlRespHtml(baiduMainLoginUrl, postDict); gLogger.Debug( "loginBaiduRespHtml=%s" , loginBaiduRespHtml) //...... } |
即可。