免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 6184 | 回复: 1
打印 上一主题 下一主题

[其他] Golang获取有重定向信息的response的location [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2015-06-01 11:21 |只看该作者 |倒序浏览
在用golang做模拟登录某个网站的功能时发现了一个问题:如何获取该网站带有重定向信息的Response?或者说我只需要
这个Response中的location信息即可,但是发现对于go的标准库net/http来说如果不hack这个库是没办法直接获取带有重
定向信息的Response,而且目前大多第三方的http库也是基于标准库net/http的,貌似就要改net/http库或库本身就提供了
对于重定向的设置?

那么我们就得先查看一下net/http库的源码了,通常我们都会用http.Client的Do方法发起一个请求,首先看一下它的源码:
  1. // Do sends an HTTP request and returns an HTTP response, following
  2. // policy (e.g. redirects, cookies, auth) as configured on the client.
  3. //
  4. // An error is returned if caused by client policy (such as
  5. // CheckRedirect), or if there was an HTTP protocol error.
  6. // A non-2xx response doesn't cause an error.
  7. //
  8. // When err is nil, resp always contains a non-nil resp.Body.
  9. //
  10. // Callers should close resp.Body when done reading from it. If
  11. // resp.Body is not closed, the Client's underlying RoundTripper
  12. // (typically Transport) may not be able to re-use a persistent TCP
  13. // connection to the server for a subsequent "keep-alive" request.
  14. //
  15. // The request Body, if non-nil, will be closed by the underlying
  16. // Transport, even on errors.
  17. //
  18. // Generally Get, Post, or PostForm will be used instead of Do.
  19. func (c *Client) Do(req *Request) (resp *Response, err error) {
  20.     if req.Method == "GET" || req.Method == "HEAD" {
  21.         return c.doFollowingRedirects(req, shouldRedirectGet)
  22.     }
  23.     if req.Method == "POST" || req.Method == "PUT" {
  24.         return c.doFollowingRedirects(req, shouldRedirectPost)
  25.     }
  26.     return c.send(req)
  27. }
复制代码
由源码的上方的注释可发现Do方法是不会返回有重定向信息的Response的,只会返回已经重定向跳转成功后的Response,而且会根据是否CheckRedirect返回error云云
那么假设我们要发起的是一个Get请求,所以进入c.doFollowingRedirects()方法中去看看
  1. func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bool) (resp *Response, err error) {
  2.     var base *url.URL
  3.     redirectChecker := c.CheckRedirect
  4.     if redirectChecker == nil {
  5.         redirectChecker = defaultCheckRedirect
  6.     }
  7.     var via []*Request

  8.     if ireq.URL == nil {
  9.         ireq.closeBody()
  10.         return nil, errors.New("http: nil Request.URL")
  11.     }

  12.     var reqmu sync.Mutex // guards req
  13.     req := ireq

  14.     var timer *time.Timer
  15.     if c.Timeout > 0 {
  16.         type canceler interface {
  17.             CancelRequest(*Request)
  18.         }
  19.         tr, ok := c.transport().(canceler)
  20.         if !ok {
  21.             return nil, fmt.Errorf("net/http: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", c.transport())
  22.         }
  23.         timer = time.AfterFunc(c.Timeout, func() {
  24.             reqmu.Lock()
  25.             defer reqmu.Unlock()
  26.             tr.CancelRequest(req)
  27.         })
  28.     }

  29.     urlStr := "" // next relative or absolute URL to fetch (after first request)
  30.     redirectFailed := false
  31.     for redirect := 0; ; redirect++ {
  32.         if redirect != 0 {
  33.             nreq := new(Request)
  34.             nreq.Method = ireq.Method
  35.             if ireq.Method == "POST" || ireq.Method == "PUT" {
  36.                 nreq.Method = "GET"
  37.             }
  38.             nreq.Header = make(Header)
  39.             nreq.URL, err = base.Parse(urlStr)
  40.             if err != nil {
  41.                 break
  42.             }
  43.             if len(via) > 0 {
  44.                 // Add the Referer header.
  45.                 lastReq := via[len(via)-1]
  46.                 if ref := refererForURL(lastReq.URL, nreq.URL); ref != "" {
  47.                     nreq.Header.Set("Referer", ref)
  48.                 }

  49.                 err = redirectChecker(nreq, via)
  50.                 if err != nil {
  51.                     redirectFailed = true
  52.                     break
  53.                 }
  54.             }
  55.             reqmu.Lock()
  56.             req = nreq
  57.             reqmu.Unlock()
  58.         }

  59.         urlStr = req.URL.String()
  60.         if resp, err = c.send(req); err != nil {
  61.             break
  62.         }


  63.         if shouldRedirect(resp.StatusCode) {

  64.             // Read the body if small so underlying TCP connection will be re-used.
  65.             // No need to check for errors: if it fails, Transport won't reuse it anyway.
  66.             const maxBodySlurpSize = 2 << 10
  67.             if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {
  68.                 io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize)
  69.             }
  70.             resp.Body.Close()
  71.             if urlStr = resp.Header.Get("Location"); urlStr == "" {
  72.                 err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode))
  73.                 break
  74.             }

  75.             base = req.URL
  76.             via = append(via, req)
  77.             continue
  78.         }
  79.         if timer != nil {
  80.             resp.Body = &cancelTimerBody{timer, resp.Body}
  81.         }
  82.         return resp, nil
  83.     }

  84.     method := ireq.Method
  85.     urlErr := &url .Error{
  86.         Op:  method[0:1] + strings.ToLower(method[1:]),
  87.         URL: urlStr,
  88.         Err: err,
  89.     }

  90.     if redirectFailed {
  91.         // Special case for Go 1 compatibility: return both the response
  92.         // and an error if the CheckRedirect function failed.
  93.         // See http://golang.org/issue/3795
  94.         return resp, urlErr
  95.     }

  96.     if resp != nil {
  97.         resp.Body.Close()
  98.     }
  99.     return nil, urlErr
  100. }
复制代码
在上述代码中发现了有检查是否进行重定向的代码:
  1. redirectChecker := c.CheckRedirect
  2.     if redirectChecker == nil {
  3.         redirectChecker = defaultCheckRedirect
  4.     }
复制代码
redirectChecker是一个这样的函数:func(req Request, via []Request) error
发现了可在client中设置自己的redirectChecker,就是只要实现了func(req Request, via []Request) error即可,其功能是由源码的
defaultCheckRedirect可知是控制重定向跳转的次数而已。
  1. func defaultCheckRedirect(req *Request, via []*Request) error {
  2.     if len(via) >= 10 {
  3.         return errors.New("stopped after 10 redirects")
  4.     }
  5.     return nil
  6. }
复制代码
defaultCheckRedirect中规定重定向跳转不能超过10次,否则返回error,那么如果我们要禁止跳转重定向的话自己实现一个
CheckRedirect,把其中的10改成0即可。但是即使这样设置后也是不能返回有重定向信息的Response,而是返回一个跳转停
止的error,不过如果你需要的只是带有重定向信息Response中的location信息的话只需要从返回的error中提取即可。http库
client中的doFollowingRedirects中传入了一个shouldRedirect函数,这个函数正是根据各种http协议的代码返回是否进行跳
转的信息,所以如果不改动这部分代码没法直接获取有重定向信息的Response,这样看来http库的设置不够灵活,得自己改或
另外实现了。

论坛徽章:
0
2 [报告]
发表于 2015-06-13 10:46 |只看该作者
我正碰上这样的,问题。登录成功后不能进入登录后的页面,在抓包中才看到,我的User-Agent被改掉了,改成“Go 1.1 package http”了,弄得Session不可用。
如果我只想重设跳转后的Header的话,你有何办法提供吗?谢谢了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP