3

我正在做的是相当直截了当的。我需要创建一个非常小且快速的“代理”服务器。目前我有一个代理到(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 addressdoRequest函数中收到错误说明,我不知道为什么。谷歌搜索这个特定的错误会产生看似无关的结果。

4

4 回答 4

5

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

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

这可能就是您耗尽本地端口的原因。我从过去的经验中知道 node.js 会非常积极地让你保持活力。

有很多小问题,每次不需要时都创建对象。创建不需要的 goroutines(每个传入的请求在你处理它之前都在它自己的 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
}
于 2013-11-06T01:12:15.767 回答
5

我们遇到了这个问题,经过大量时间尝试调试后,我遇到了这个问题: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)
于 2014-05-06T18:19:46.853 回答
1

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

于 2013-11-15T03:50:24.267 回答
0

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

于 2016-09-05T11:26:18.380 回答