【问题标题】:Getting "127.0.0.1 can't assign requested address" - http.Client获取“127.0.0.1 无法分配请求的地址” - http.Client
【发布时间】:2013-11-08 00:25:15
【问题描述】:

我正在做的是相当直截了当的。我需要创建一个非常小且快速的“代理”服务器。目前我有一个代理到(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
  }
}

我的问题,正如标题中提到的那样,是我从 doRequest 函数中收到错误声明 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,我不知道为什么。谷歌搜索这个特定错误会产生看似无关的结果。

【问题讨论】:

  • 出于好奇,您在这里使用的端口80809000 有什么区别?
  • 主服务运行在端口8080,代理服务运行在端口9000,并且会透明地调用8080上的服务(或者这就是它最终的工作方式)。
  • 您忽略了来自resp.Write 调用在proxyHandlerFunc 中的错误返回:这可以为您的问题提供线索吗?该错误听起来类似于groups.google.com/d/topic/golang-nuts/K0iAoVhAouE/discussion,但我原以为resp.Write() 应该在成功完成后关闭正文。也许手动关闭身体可能会有所帮助。
  • 我会添加一个检查,看看是否能解决任何问题,但是在挖掘文档和源代码后,连接并没有在写入时关闭。
  • 添加了检查,写操作没有返回错误。

标签: http go


【解决方案1】:

这段代码有两个主要问题。

  1. 您没有处理客户端停顿或使用保活(下面由 getTimeoutServer 处理)
  2. 您没有处理服务器(您的 http.Client 正在与之通信的内容)超时(下面由 TimeoutConn 处理)。

这可能就是您耗尽本地端口的原因。我从过去的经验中知道 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
}

【讨论】:

    【解决方案2】:

    我们遇到了这个问题,在尝试调试了很多时间后,我遇到了这个问题: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)
    

    【讨论】:

    • 你是最好的 Travis R。谢谢你。
    【解决方案3】:

    我也遇到过这个问题,我在http.Transport中添加了一个选项{DisableKeepAlives: true}解决了这个问题,你可以试试。

    【讨论】:

      【解决方案4】:

      我来这里是在一个系统上每秒运行大量 SQL 查询而没有长时间限制空闲连接数的时候。正如this issue comment on github 中指出的,明确设置db.SetMaxIdleConns(5) 完全解决了我的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-09-09
        • 2016-06-23
        • 2021-11-03
        • 2013-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多