获取“127.0.0.1无法分配请求的地址” - http.Client

时间:2013-10-29 01:43:16

标签: http go

我正在做的事情非常简单。我需要创建一个非常小而快的“代理”服务器。目前我有一个代理到(nodejs)和代理服务(go)的基线服务器。请原谅缺乏实际的“代理” - 现在只是测试。

基线服务

var http = require('http');
http.createServer(function (req, res) {
    // console.log("received request");
    res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end('Hello World\n');
}).listen(8080, '127.0.0.1');
console.log('Server running at http://127.0.0.1:8080/');

代理服务

package main

import (
  "flag"
  "log"
  "net/http"
  "net/url"
)

var (
  listen = flag.String("listen", "0.0.0.0:9000", "listen on address")
  logp = flag.Bool("log", false, "enable logging")
)

func main() {
  flag.Parse()
  proxyHandler := http.HandlerFunc(proxyHandlerFunc)
  log.Fatal(http.ListenAndServe(*listen, proxyHandler))
  log.Println("Started router-server on 0.0.0.0:9000")
}

func proxyHandlerFunc(w http.ResponseWriter, r *http.Request) {
  // Log if requested
  if *logp {
    log.Println(r.URL)
  }

  /* 
   * Tweak the request as appropriate:
   *   - RequestURI may not be sent to client
   *   - Set new URL
   */
  r.RequestURI = ""
  u, err := url.Parse("http://localhost:8080/")
  if err != nil {
    log.Fatal(err)
  }
  r.URL = u

  // And proxy
  // resp, err := client.Do(r)
  c := make(chan *http.Response)
  go doRequest(c)
  resp := <-c
  if resp != nil {
    err := resp.Write(w)
    if err != nil {
      log.Println("Error writing response")
    } else {
      resp.Body.Close()
    }
  }
}


func doRequest(c chan *http.Response) {
  // new client for every request.
  client := &http.Client{}

  resp, err := client.Get("http://127.0.0.1:8080/test")
  if err != nil {
    log.Println(err)
    c <- nil
  } else {
    c <- resp
  }
}

我在标题中提到的问题是,我从2013/10/28 21:22:30 Get http://127.0.0.1:8080/test: dial tcp 127.0.0.1:8080: can't assign requested address函数中收到错误doRequest,我不知道为什么。谷歌搜索此特定错误会产生看似无关的结果。

4 个答案:

答案 0 :(得分:5)

此代码存在两个主要问题。

  1. 您没有处理客户端停止或使用keep alives(由getTimeoutServer处理)
  2. 您没有处理服务器(您的http.Client正在与之交谈)超时(由TimeoutConn在下面处理)。
  3. 这可能是您耗尽本地端口的原因。我从过去的经验中知道node.js会非常积极地保持活力。

    有很多小问题,每次都不需要时创建对象。创建不需要的goroutine(在处理之前,每个传入的请求都在它自己的goroutine中。)

    这是一个快速刺(我没时间测试好)。希望它能让你走上正确的轨道:(你需要升级它以不在本地缓冲响应)

    package main
    
    import (
        "bytes"
        "errors"
        "flag"
        "fmt"
        "log"
        "net"
        "net/http"
        "net/url"
        "runtime"
        "strconv"
        "time"
    )
    
    const DEFAULT_IDLE_TIMEOUT = 5 * time.Second
    
    var (
        listen       string
        logOn        bool
        localhost, _ = url.Parse("http://localhost:8080/")
        client       = &http.Client{
            Transport: &http.Transport{
                Proxy: NoProxyAllowed,
                Dial: func(network, addr string) (net.Conn, error) {
                    return NewTimeoutConnDial(network, addr, DEFAULT_IDLE_TIMEOUT)
                },
            },
        }
    )
    
    func main() {
        runtime.GOMAXPROCS(runtime.NumCPU())
        flag.StringVar(&listen, "listen", "0.0.0.0:9000", "listen on address")
        flag.BoolVar(&logOn, "log", true, "enable logging")
        flag.Parse()
        server := getTimeoutServer(listen, http.HandlerFunc(proxyHandlerFunc))
        log.Printf("Starting router-server on %s\n", listen)
        log.Fatal(server.ListenAndServe())
    }
    
    func proxyHandlerFunc(w http.ResponseWriter, req *http.Request) {
        if logOn {
            log.Printf("%+v\n", req)
        }
        // Setup request URL
        origURL := req.URL
        req.URL = new(url.URL)
        *req.URL = *localhost
        req.URL.Path, req.URL.RawQuery, req.URL.Fragment = origURL.Path, origURL.RawQuery, origURL.Fragment
        req.RequestURI, req.Host = "", req.URL.Host
        // Perform request
        resp, err := client.Do(req)
        if err != nil {
            w.WriteHeader(http.StatusBadGateway)
            w.Write([]byte(fmt.Sprintf("%d - StatusBadGateway: %s", http.StatusBadGateway, err)))
            return
        }
        defer resp.Body.Close()
        var respBuffer *bytes.Buffer
        if resp.ContentLength != -1 {
            respBuffer = bytes.NewBuffer(make([]byte, 0, resp.ContentLength))
        } else {
            respBuffer = new(bytes.Buffer)
        }
        if _, err = respBuffer.ReadFrom(resp.Body); err != nil {
            w.WriteHeader(http.StatusBadGateway)
            w.Write([]byte(fmt.Sprintf("%d - StatusBadGateway: %s", http.StatusBadGateway, err)))
            return
        }
        // Write result of request
        headers := w.Header()
        var key string
        var val []string
        for key, val = range resp.Header {
            headers[key] = val
        }
        headers.Set("Content-Length", strconv.Itoa(respBuffer.Len()))
        w.WriteHeader(resp.StatusCode)
        w.Write(respBuffer.Bytes())
    }
    
    func getTimeoutServer(addr string, handler http.Handler) *http.Server {
        //keeps people who are slow or are sending keep-alives from eating all our sockets
        const (
            HTTP_READ_TO  = DEFAULT_IDLE_TIMEOUT
            HTTP_WRITE_TO = DEFAULT_IDLE_TIMEOUT
        )
        return &http.Server{
            Addr:         addr,
            Handler:      handler,
            ReadTimeout:  HTTP_READ_TO,
            WriteTimeout: HTTP_WRITE_TO,
        }
    }
    
    func NoProxyAllowed(request *http.Request) (*url.URL, error) {
        return nil, nil
    }
    
    //TimeoutConn-------------------------
    //Put me in my own TimeoutConn.go ?
    
    type TimeoutConn struct {
        net.Conn
        readTimeout, writeTimeout time.Duration
    }
    
    var invalidOperationError = errors.New("TimeoutConn does not support or allow .SetDeadline operations")
    
    func NewTimeoutConn(conn net.Conn, ioTimeout time.Duration) (*TimeoutConn, error) {
        return NewTimeoutConnReadWriteTO(conn, ioTimeout, ioTimeout)
    }
    
    func NewTimeoutConnReadWriteTO(conn net.Conn, readTimeout, writeTimeout time.Duration) (*TimeoutConn, error) {
        this := &TimeoutConn{
            Conn:         conn,
            readTimeout:  readTimeout,
            writeTimeout: writeTimeout,
        }
        now := time.Now()
        err := this.Conn.SetReadDeadline(now.Add(this.readTimeout))
        if err != nil {
            return nil, err
        }
        err = this.Conn.SetWriteDeadline(now.Add(this.writeTimeout))
        if err != nil {
            return nil, err
        }
        return this, nil
    }
    
    func NewTimeoutConnDial(network, addr string, ioTimeout time.Duration) (net.Conn, error) {
        conn, err := net.DialTimeout(network, addr, ioTimeout)
        if err != nil {
            return nil, err
        }
        if conn, err = NewTimeoutConn(conn, ioTimeout); err != nil {
            return nil, err
        }
        return conn, nil
    }
    
    func (this *TimeoutConn) Read(data []byte) (int, error) {
        this.Conn.SetReadDeadline(time.Now().Add(this.readTimeout))
        return this.Conn.Read(data)
    }
    
    func (this *TimeoutConn) Write(data []byte) (int, error) {
        this.Conn.SetWriteDeadline(time.Now().Add(this.writeTimeout))
        return this.Conn.Write(data)
    }
    
    func (this *TimeoutConn) SetDeadline(time time.Time) error {
        return invalidOperationError
    }
    
    func (this *TimeoutConn) SetReadDeadline(time time.Time) error {
        return invalidOperationError
    }
    
    func (this *TimeoutConn) SetWriteDeadline(time time.Time) error {
        return invalidOperationError
    }
    

答案 1 :(得分:4)

我们遇到了这个问题,经过大量时间尝试调试后,我遇到了这个问题:https://code.google.com/p/go/source/detail?r=d4e1ec84876c

  

这将负担转移到客户端以阅读他们的整个回复   如果他们想要重用TCP连接的优势,可以使用。

所以一定要在关闭之前阅读整个身体,有几种方法可以做到。此功能可以派上用场,通过记录尚未读取的额外字节并为您清理流来让您查看是否存在此问题,以便它可以重用该连接:

func closeResponse(response *http.Response) error {
    // ensure we read the entire body
    bs, err2 := ioutil.ReadAll(response.Body)
    if err2 != nil {
        log.Println("Error during ReadAll!!", err2)
    }
    if len(bs) > 0 {
        log.Println("Had to read some bytes, not good!", bs, string(bs))
    }
    return response.Body.Close()
}

或者,如果你真的不关心身体,你可以用它来丢弃它:

io.Copy(ioutil.Discard, response.Body)

答案 2 :(得分:1)

我也遇到过这个问题,我向http.Transport添加了一个选项{DisableKeepAlives:true}修复了这个问题,你可以尝试一下。

答案 3 :(得分:0)

我在系统上每秒运行大量SQL查询时来到这里,而不会在很长一段时间内限制空闲连接的数量。正如this issue comment on github中明确指出的那样db.SetMaxIdleConns(5)完全解决了我的问题。