1

https://golang.org/src/net/http/httptrace/trace.go#L79 allows hooks for client side of http connection. Is there any server side equivalent for WroteHeaders , I.e. something along the lines RecievedHeaders.

I checked go source and could not find relevant details. I would like to be able to respond to request as quickly as possible, which may be as simple aa close connection if a given header is missing.

1 Answers1

2

No such built-in callback is supported by the standard library, but you may create one easily.

Denying based on incoming (request) headers

This is simple. Just start your handler with validating the incoming requests headers. This is sufficient, because your handler function is called once incoming HTTP headers are processed (read) but not the request body. And to tell if an incoming header is missing, you'd have to read all the HTTP headers anyway.

Denying based on remote IP

If you want to reject a request based on remote IP, you may create and use your own http.Server instance, and it has a Server.ConnState field where you can set a callback function. This callback will be called for state changes detailed at http.ConnState. StateNew is fired for new connections, StateActive is fired before any handler would be called. Although you cannot signal in the callback that you want to reject the request, you can always close the connection which is passed to the callback.

If you don't want to do this, you may still check and reject serving the request in the handler (before the request body is read / processed), for details see How to limit client IP address when using golang http package.

Denying based on outgoing (response) headers

What you may do is mock the http.ResponseWriter. Before any data is sent back to the client as the response, HTTP status code and HTTP headers have to be written. This is triggered by the ResponseWriter.WriteHeader() method. So wrap the original http.ResponseWriter, provide your own implementation of WriteHeader() in which you may check the headers that are set and are about to be sent. During the check if you find some header is missing, you may decide to send back an error and stop serving the data that would be written by "downstream" handlers.

This is an example mocked ResponseWriter:

type MyResponseWriter struct {
    http.ResponseWriter
    err error
}

func (mrw *MyResponseWriter) WriteHeader(statusCode int) {
    // Inspect headers:
    headers := mrw.Header()
    if len(headers["myheader"]) == 0 {
        mrw.ResponseWriter.WriteHeader(http.StatusInternalServerError)
        mrw.err = fmt.Errorf("myheader was not set")
        return
    }

    // Headers ok, let call "through":
    mrw.ResponseWriter.WriteHeader(statusCode)
}

func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
    if mrw.err != nil {
        return 0, mrw.err
    }
    return mrw.ResponseWriter.Write(p)
}

What it does is in MyResponseWriter.WriteHeader() it looks for the HTTP header "myheader". If this is missing, reports HTTP 500 internal server error to the client, and skips all Write() (filters out response that would be sent by the handler).

Here's a simple example using it for a specific handler:

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

func main() {
    http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
        w2 := &MyResponseWriter{ResponseWriter: w}
        fooHandler(w2, r)
    })
    // ...
}

Note that to save resources, MyResponseWriter should communicate that it will not "forward" data to the client, so subsequent handlers could omit writing data. This could be achieved by providing a method which handlers could check using a type assertion, or it may replace the request's context and cancel that (handlers must "monitor" the context for this to work of course).

Also see Golang read request body multiple times (which details mocking the request and response).

icza
  • 389,944
  • 63
  • 907
  • 827
  • Your solution is nice however it doesn't solve the problem I am trying to address. Headers may be a few KBs and body could be any size. I want reader to block until decision has been made based on remote address and headers sent BEFORE unblocking reader or terminating the connection. Closest functionality is http.hijacker(), but then I loose all inbuilt functionality provided by go. – i_am_on_my_way_to_happiness Apr 05 '18 at 11:30
  • @AmitTewari I thought you want to reject based on response headers. See edited answer where I detail how to do it based on request headers or remote IP. – icza Apr 05 '18 at 11:45