Modifying the response body of an httputil.ReverseProxy
response
![Featured image for sharing metadata for article](https://media.jvt.me/b41202acf7.png)
I'm currently working with the maintainer of the beautiful sql-studio
to make it possible to serve the application from a subpath, which would allow me to add support for running sql-studio
as dependency-management-data's web interface.
As part of some investigations around this, I wanted to make a somewhat cursed attempt to perform some server-side rewriting so I could make changes like:
- <script type="module" crossorigin src="/assets/index-Dc4xAj-D.js"></script>
- <link rel="stylesheet" crossorigin href="/assets/index-DpDMcPjm.css">
+ <script type="module" crossorigin src="/sql-studio/assets/index-Dc4xAj-D.js"></script>
+ <link rel="stylesheet" crossorigin href="/sql-studio/assets/index-DpDMcPjm.css">
</head>
To do this, I've been using httputil.ReverseProxy
, with an initial setup like:
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
u, err := url.Parse("http://localhost:3030")
if err != nil {
log.Fatal(err)
}
proxy := httputil.NewSingleHostReverseProxy(u)
mux := http.NewServeMux()
mux.Handle("/sql-studio", proxy)
log.Fatal(http.ListenAndServe(":8080", mux))
}
To perform the rewriting inside the proxy, I found this StackOverflow which indicated that I should use the ModifyResponse
function, which allows us to do something like:
package main
import (
"bytes"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
)
func main() {
u, err := url.Parse("http://localhost:3030")
if err != nil {
log.Fatal(err)
}
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.ModifyResponse = func(r *http.Response) error {
b, err := io.ReadAll(r.Body)
if err != nil {
return err
}
defer r.Body.Close()
// this is pretty hacky, but as a step towards https://github.com/frectonz/sql-studio/issues/16 in the meantime of upstream support
b = bytes.Replace(b,
[]byte("href=\"/"),
[]byte("href=\"/sql-studio/"),
-1)
// ...
// make sure we set the body, and the relevant headers for well-formed clients to respect
r.Body = io.NopCloser(bytes.NewReader(b))
r.ContentLength = int64(len(b))
r.Header.Set("Content-Length", strconv.Itoa(len(b)))
return nil
}
mux := http.NewServeMux()
mux.Handle("/sql-studio/", proxy)
log.Fatal(http.ListenAndServe(":8080", mux))
}
Et voila, we can now rewrite the response body from the proxied service, but before it goes back to the caller.