Getting "127.0.0.1 can't assign requested address" - http.Client

10,422

Solution 1

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

Solution 2

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)

Solution 3

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

Share:
10,422
Admin
Author by

Admin

Updated on August 26, 2022

Comments

  • Admin
    Admin over 1 year

    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\n');
    }).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.