Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing the underlying socket of a net/http response

Tags:

go

sockets

I'm new to Go and evaluating it for a project.

I'm trying to write a custom handler to serve files with net/http. I can't use the default http.FileServer() handler because I need to have access to the underlying socket (the internal net.Conn) so I can perform some informational platform specific "syscall" calls on it (mainly TCP_INFO).

More precisly: I need to access the underlying socket of the http.ResponseWriter in the handler function:

func myHandler(w http.ResponseWriter, r *http.Request) {
...
// I need the net.Conn of w
...
}

used in

http.HandleFunc("/", myHandler)

Is there a way to this. I looked at how websocket.Upgrade does this but it uses Hijack() which is 'too much' because then I have to code 'speaking http' over the raw tcp socket I get. I just want a reference to the socket and not taking over completely.

like image 335
KGJV Avatar asked Apr 09 '15 07:04

KGJV


2 Answers

Note that although in current implementation http.ResponseWriter is a *http.response (note the lowercase!) which holds the connection, the field is unexported and you can't access it.

Instead take a look at the Server.ConnState hook: you can "register" a function which will be called when the connection state changes, see http.ConnState for details. For example you will get the net.Conn even before the request enters the handler (http.StateNew and http.StateActive states).

You can install a connection state listener by creating a custom Server like this:

func main() {
    http.HandleFunc("/", myHandler)

    s := &http.Server{
        Addr:           ":8081",
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
        ConnState:      ConnStateListener,
    }
    panic(s.ListenAndServe())
}

func ConnStateListener(c net.Conn, cs http.ConnState) {
    fmt.Printf("CONN STATE: %v, %v\n", cs, c)
}

This way you will have exactly the desired net.Conn even before (and also during and after) invoking the handler. The downside is that it is not "paired" with the ResponseWriter, you have to do that manually if you need that.

like image 138
icza Avatar answered Sep 27 '22 21:09

icza


This can be done with reflection. it's a bit "dirty" but it works:

package main

import "net/http"
import "fmt"
import "runtime"

import "reflect"

func myHandler(w http.ResponseWriter, r *http.Request) {

    ptrVal := reflect.ValueOf(w)
    val := reflect.Indirect(ptrVal)

    // w is a "http.response" struct from which we get the 'conn' field
    valconn := val.FieldByName("conn")
    val1 := reflect.Indirect(valconn)

    // which is a http.conn from which we get the 'rwc' field
    ptrRwc := val1.FieldByName("rwc").Elem()
    rwc := reflect.Indirect(ptrRwc)

    // which is net.TCPConn from which we get the embedded conn
    val1conn := rwc.FieldByName("conn")
    val2 := reflect.Indirect(val1conn)

    // which is a net.conn from which we get the 'fd' field
    fdmember := val2.FieldByName("fd")
    val3 := reflect.Indirect(fdmember)

    // which is a netFD from which we get the 'sysfd' field
    netFdPtr := val3.FieldByName("sysfd")
    fmt.Printf("netFDPtr= %v\n", netFdPtr)

    // which is the system socket (type is plateform specific - Int for linux)
    if runtime.GOOS == "linux" {
        fd := int(netFdPtr.Int())
        fmt.Printf("fd = %v\n", fd)
        // fd is the socket - we can call unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(fd),....) on it for instance
    }

    fmt.Fprintf(w, "Hello World")
}

func main() {
    http.HandleFunc("/", myHandler)
    err := http.ListenAndServe(":8081", nil)
    fmt.Println(err.Error())
}

Ideally the library should be augmented with a method to get the underlying net.Conn

like image 40
KGJV Avatar answered Sep 27 '22 21:09

KGJV