【背景】
折腾:
期间,需要去发送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相关代码了。
然后是用下面核心代码:
/*
 * [File]
 * EmulateLoginBaidu.go
 * 
 * [Function]
 * 【记录】用go语言实现模拟登陆百度
 * https://www.crifan.org/emulate_login_baidu_using_go_language/
 * 
 * [Version]
 * 2013-09-21
 *
 * [Contact]
 * https://www.crifan.org/about/me/
 */
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 ======");
        staticPageUrl := "http://www.baidu.com/cache/user/html/jump.html";
        
        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)
        
        baiduMainLoginUrl := "https://passport.baidu.com/v2/api/?login";
        loginBaiduRespHtml := getUrlRespHtml(baiduMainLoginUrl, postDict);
        gLogger.Debug("loginBaiduRespHtml=%s", loginBaiduRespHtml)
    }
 }实现了可以成功发送POST,传递进入post data:
[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的:
[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">
var url = encodeURI('http://www.baidu.com/cache/user/html/jump.html?hao123Param=RzFMTkc1dUxUTllXa1UyYnpBMlNHeENaSFoyUm01b2NuTi1NRWhGZW01dVFrWnJka0pGVm1wbFVtcEJWMVpUUVZGQlFVRkJKQ1FBQUFBQUFBQUFBQUVBQUFCLU9VZ0NZV2RoYVc1cGJuQjFkQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBR04wUFZKamREMVNN&callback=parent.bdPass.api.login._postCallback&index=0&codestring=&username=xxxxxx&phonenumber=&mail=&tpl=mn&u=https%3A%2F%2Fpassport.baidu.com%2F&needToModifyPassword=0&gotourl=&auth=&error=0');
//parent.callback(url)
window.location.replace(url);
</script>
</body>
</html>如图:
至此,至少基本的POST,是搞定了。
【总结】
此处,对于http的POST,且要传递对应的post data的话,最最核心的代码是:
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"
    //......        
    
    baiduMainLoginUrl := "https://passport.baidu.com/v2/api/?login";
    loginBaiduRespHtml := getUrlRespHtml(baiduMainLoginUrl, postDict);
    gLogger.Debug("loginBaiduRespHtml=%s", loginBaiduRespHtml)
    
    //......
}即可。
