Есть ли способ использовать определенный TCPConn для выполнения HTTP-запросов? - PullRequest
1 голос
/ 04 октября 2019

Я пытаюсь настроить способ связи между центральным сервером, на котором установлен Go, и парком устройств IoT (также работающим на Go).

Для каждого устройства оно подключается к центральному серверу через постоянный TCPConn. Эти устройства находятся за маршрутизатором (ами). Центральный сервер сохраняет это соединение и отправляет / получает сообщения через него. Прямо сейчас, это полностью функционально и работает.

Однако теперь передача сообщений становится достаточно сложной, так что становится необходимой утилита, предоставляемая HTTP, а не просто TCP.

Я пыталсянапишите версию http.Transport, которая возвращает указанное соединение. Однако я не могу предоставить и вернуть действительное соединение с помощью функций Dial / DialContext.

IoT-устройство

func main() {

    http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
    })

    tcpAddr, err := net.ResolveTCPAddr("tcp", "###.###.###.###:8533")
    if err != nil {
        panic(err)
    }

    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    if err != nil {
        panic(err)
    }

    err = conn.SetKeepAlive(true)
    if err != nil {
        panic(err)
    }

    err = conn.SetKeepAlivePeriod(time.Second * 10)
    if err != nil {
        panic(err)
    }

    fmt.Println("Listening")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

Центральный сервер

func main() {
    tcpAddr, err := net.ResolveTCPAddr("tcp", port)
    if err != nil {
        panic(err)
    }
    listener, err := net.ListenTCP("tcp", tcpAddr)

    if err != nil {
        panic(err)
    }

    conn, err := listener.AcceptTCP()
    if err != nil {
        panic(err)
    }
    fmt.Println("Received conn, attempting to send HTTP through connection")

    dialFunc := func(network, addr string) (net.Conn, error) {
        return conn, nil
    }

    t := http.Transport{
        Dial: dialFunc,
    }

    client := http.Client{
        Transport: &t,
    }

    fmt.Println("Making request")
    res, err := client.Get("http://www.shouldNotMatter.com:8080/foo") // HANGS HERE
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println("Received response")
    defer res.Body.Close()

    if res.StatusCode == http.StatusOK {
        bodyBytes, err := ioutil.ReadAll(res.Body)
        if err != nil {
            panic(err)
        }
        bodyString := string(bodyBytes)
        fmt.Println(bodyString)
    } else {
        fmt.Println(res)
    }
}

При использованииотладчик, чтобы увидеть, где он висит, кажется, что он застревает в операторе select во время обхода pconn. Строка 2420 в https://golang.org/src/net/http/transport.go?s=3397:10477

Ответы [ 2 ]

3 голосов
/ 04 октября 2019

Создание типа, который возвращает существующее соединение из метода набора номера:

type connDialer struct {
    c net.Conn
}

func (cd connDialer) Dial(network, addr string) (net.Conn, error) {
    return cd.c, nil
}

Использование набора номера значение метода в транспорте:

client := http.Client{Transport: &http.Transport{Dial: connDialer{c}.Dial}}

, гдеc - это существующий net.Conn.

Попробуйте на игровой площадке (работает по одному запросу. Сбой при наборе клиентом второго соединения).

Общий подход хрупок. Рассмотрите возможность использования WebSockets, gRPC или других протоколов, предназначенных для поддержки двунаправленной связи.

0 голосов
/ 06 октября 2019

Вы пропустили код в коде клиента. Клиент устанавливает простое соединение с сервером и ничего с ним не делает, поэтому соединение обязательно прервется. Вам необходимо передать соединение на HTTP-сервер. Это может быть достигнуто путем использования net/http.Serve и передачи ему net.Listener.

type connListener struct {
    conn net.Conn
    ch chan struct{}
}
func (c connListener) Accept() (Conn, error) {
    if c.conn != nil {
        conn := c.conn
        c.conn = nil
        return conn, nil
    }
    <-c.ch
    return nil, errors.New("listener closed")
}
func (c connListener) Close() error {
    close(c.ch)
    return nil
}
func (c connListener) Addr() net.Addr {
    return c.conn.LocalAddr()
}

// call it like this
http.Serve(connListener{conn, make(chan struct{})}, nil)

Кстати, если клиент подключается к серверу, а затем отменяет соединение, делаяклиент ведет себя как HTTP-сервер, а сервер ведет себя как HTTP-клиент? Возможно, вы захотите зайти в Google по ссылке «http» для получения дополнительной информации по этому вопросу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...