3

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.

  • Out of curiosity, what's the difference between your uses of ports `8080` and `9000` here? – Christian Ternus Oct 29 '13 at 01:45
  • The main service runs on port `8080`, the proxy service runs on port `9000` and will call the service on 8080 transparently (or so that's the way it will eventually work). –  Oct 29 '13 at 01:53
  • You're ignoring the error return from the `resp.Write` call in `proxyHandlerFunc`: might that give a clue to your problem? The error sounds similar to https://groups.google.com/d/topic/golang-nuts/K0iAoVhAouE/discussion, but I would have thought `resp.Write()` should be closing the body if it completes successfully. Perhaps manually closing the body might help. – James Henstridge Oct 29 '13 at 02:04
  • I'll add a check and see if that resolves anything, however after digging through the docs and source, the connection is not closed on write. –  Oct 29 '13 at 02:10
  • Added a check and there are no errors coming back from the write operation. –  Oct 29 '13 at 02:12
  • If you're on Linux have a look at http://stackoverflow.com/questions/3886506/why-would-connect-give-eaddrnotavail . In particular the part about "The specified address is unavailable on the remote machine or the address field of the name structure is all zeroes." – Intermernet Oct 29 '13 at 07:40
  • Interesting, now if I could just determine what is keeping the TCP connection open. That was hinted at in the IRC, but still not sure what I should be closing (besides the body). Perhaps I'm creating go-routines faster than I can close sockets. –  Oct 29 '13 at 14:07
  • How much requests do you send before error is catched? – mechmind Oct 30 '13 at 23:38
  • You have already better utilities for proxy code. Check http://golang.org/pkg/net/http/httputil/#NewProxyClientConn and http://golang.org/pkg/net/http/httputil/#ReverseProxy – Tomás Senart Nov 03 '13 at 13:23
  • But related to the question itself, could you paste an actual backtrace? – Tomás Senart Nov 03 '13 at 13:24
  • John, did my answer below help? – voidlogic Nov 08 '13 at 16:56

4 Answers4

5

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
}
voidlogic
  • 6,398
  • 2
  • 23
  • 21
5

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)
Travis Reeder
  • 38,611
  • 12
  • 87
  • 87
1

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

oli
  • 86
  • 1
  • 2
1

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.

Friedrich Große
  • 2,383
  • 19
  • 19
  • This! I usually configure everything correctly but I forgot this one value and was debugging since half a day. Found a bunch of config changes to do and did those and was getting really impatient. Your comment has been really helpful (after 5 and a half years!). Thank you. – Vaibhav Apr 03 '22 at 10:20