Modifying the response body of an httputil.ReverseProxy response

Featured image for sharing metadata for article

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.

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #go.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.