httpgohttp-status

Extending golang's http package


I would need to extend http package to achieve non standard response containing error description in status ie: 400 Required parameter is missing instead of 400 Bad request that is standard status description.

This is my actual implementation:

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "net/url"
)

type GatewayHandler int

func main() {
    var gh GatewayHandler

    http.ListenAndServe(":9000", gh)
}

func (gh GatewayHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {

    legacyApiUrl := "http://some-url.com" + req.URL.RequestURI()

    client := &http.Client{}
    request, _ := http.NewRequest(req.Method, legacyApiUrl, nil)
    response, _ := client.Do(request)
    res.Header().Set("Status", response.Status)
    for k, v := range response.Header {
        fmt.Println(k, ": ", v)
        i := ""
        for _, j := range v {
            i += j
        }
        res.Header().Set(k, i)
    }

    res.WriteHeader(response.StatusCode)

    if response.Status != "200 OK" {
        fmt.Println(response.Status)
    }

    result, _ := ioutil.ReadAll(response.Body)
    output := string(result)
    fmt.Println(output)

    io.WriteString(res, output)
}

In general I need to forward that status from other URL that uses it and I need to keep it compatible.

Thank you very much in advance.

Jozef


Solution

  • You can use the http.Hijacker interface https://golang.org/pkg/net/http/#Hijacker to "hijack" (take over) the server's TCP connection to the client and write to it the custom response. Here is a modification of the example https://golang.org/pkg/net/http/#example_Hijacker to return "400 Required parameter is missing" instead of the standard "400 Bad request" response to the client:

    package main
    
    import "net/http"
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            hj, ok := w.(http.Hijacker)
            if !ok {
                http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
                return
            }
            conn, bufrw, err := hj.Hijack()
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            // Don't forget to close the connection:
            defer conn.Close()
            // non-standard HTTP status text and an HTTP header are written;
            // end of the Headers part of the messages is marked by extra \r\n
            bufrw.WriteString("HTTP/1.1 400 Required parameter is missing\r\nContent-Type: text/html; charset=utf-8\r\n\r\n")
            // write the body of the HTTP response message
            bufrw.WriteString("400 Required parameter is missing\n")
            bufrw.Flush()
        })
        http.ListenAndServe(":9000", nil)
    }
    

    Running this program and sending a curl request produces the desired response:

    $ curl -i http://localhost:9000/
    HTTP/1.1 400 Required parameter is missing
    Content-Type: text/html; charset=utf-8
    
    400 Required parameter is missing
    

    It should be straightforward to extend it to propagate other responses from your legacy API server.

    Edit
    used \r\n\r\n in the example program to terminate the Headers portion of the HTTP response in accordance with the HTTP message standard (https://www.rfc-editor.org/rfc/rfc7230#section-3); separated WriteString calls for the headers and body of the HTTP response for clarity.