dongrenshi0889
2013-10-29 01:43
浏览 231
已采纳

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

What I'm doing is fairly straight-forward. I need to create a "proxy" server that is very minimal and fast. Currently I have a baseline server that is proxied to (nodejs) and a proxy-service (go). Please excuse the lack of actual "proxy'ing" - just testing for now.

Baseline Service

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

Proxy Service

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
  }
}

My issue, as mentioned within the title, is that I am getting errors stating 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 from the doRequest function, and I have no clue why. Googling this particular error yields seemingly irrelevant results.

  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

4条回答 默认 最新

  • dsgrgaz321973284 2013-11-06 01:12
    已采纳

    There are 2 major problems with this code.

    1. You are not handling the client stalling or using keep alives (handled below by getTimeoutServer)
    2. You are not handling the server (what your http.Client is talking to) timing out (handled below by TimeoutConn).

    This is probably why you are exhausting your local ports. I know from past experience node.js will keep-alive you very aggressively.

    There are lots of little issues, creating objects every-time when you don't need to. Creating unneeded goroutines (each incoming request is in its own goroutine before you handle it).

    Here is a quick stab (that I don't have time to test well). Hopefully it will put you on the right track: (You will want to upgrade this to not buffer the responses locally)

    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
    ", listen)
        log.Fatal(server.ListenAndServe())
    }
    
    func proxyHandlerFunc(w http.ResponseWriter, req *http.Request) {
        if logOn {
            log.Printf("%+v
    ", 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
    }
    
    已采纳该答案
    打赏 评论
  • douchaqi3369 2013-11-15 03:50

    I have encountered this problem too, and i add an option {DisableKeepAlives: true} to http.Transport fixed this issue, you can have a try.

    打赏 评论
  • dongzhou1901 2014-05-06 18:19

    We ran into this and after a lot of time trying to debug, I came across this: https://code.google.com/p/go/source/detail?r=d4e1ec84876c

    This shifts the burden onto clients to read their whole response bodies if they want the advantage of reusing TCP connections.

    So be sure you read the entire body before closing, there are a couple of ways to do it. This function can come in handy to close to let you see whether you have this issue by logging the extra bytes that haven't been read and cleaning the stream out for you so it can reuse the connection:

    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()
    }
    

    Or if you really don't care about the body, you can just discard it with this:

    io.Copy(ioutil.Discard, response.Body)
    
    打赏 评论
  • dpwle46882 2016-09-05 11:26

    I came here when running a massive amount of SQL queries per second on a system without limiting the number of idle connections over a long period of time. As pointed out in this issue comment on github explicitly setting db.SetMaxIdleConns(5) completely solved my problem.

    打赏 评论

相关推荐 更多相似问题