shurcooL / home

home is Dmitri Shuralyov's personal website.
https://dmitri.shuralyov.com
MIT License
76 stars 2 forks source link

consider setting up a fallback mechanism to serve existing versions of Go modules in case of provider outage #38

Open dmitshur opened 4 years ago

dmitshur commented 4 years ago

If the server that is hosting the https://dmitri.shuralyov.com website is unavailable due to an outage of its current server provider (as has happened recently in #37), it may be worth considering a fallback plan.

It can be a quick-to-start second instance, or perhaps a more static page that makes use of the Go module versions already cached by https://proxy.golang.org and serving those instead.

This is the tracking issue to investigate and implement this.

Note that as more and more people update to Go 1.13 or newer, which uses https://proxy.golang.org by default, the benefit from doing this will diminish.

dmitshur commented 4 years ago

or perhaps a more static page that makes use of the Go module versions already cached by https://proxy.golang.org and serving those instead.

I've tested out the following simple approach locally (by adding a dmitri.shuralyov.com→127.0.0.1 override to /etc/hosts and using @FiloSottile's mkcert to serve the replacement site over https), and confirmed that it would work as I expected:

// Play with a fallback solution for serving dmitri.shuralyov.com modules,
// when the main dmitri.shuralyov.com server is completely unavailable.
//
// The fallback solution is to host a very simple site that just temporarily
// points to the modules that have already been mirrored by proxy.golang.org.
package main

import (
    "log"
    "net/http"
    "net/url"
    "os"

    "github.com/shurcooL/home/httputil"
    "golang.org/x/net/html"
)

func main() {
    err := run()
    if err != nil {
        log.Fatalln(err)
    }
}

func run() error {
    h := httputil.ErrorHandler(nil, func(w http.ResponseWriter, req *http.Request) error {
        if req.URL.RawQuery != "go-get=1" {
            return os.ErrNotExist
        }
        if err := httputil.AllowMethods(req, http.MethodGet); err != nil {
            return err
        }
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        err := html.Render(w, &html.Node{
            Type: html.ElementNode, Data: "meta",
            Attr: []html.Attribute{
                {Key: "name", Val: "go-import"},
                {Key: "content", Val: (&url.URL{Path: "dmitri.shuralyov.com" + req.URL.Path}).String() +
                    " mod https://proxy.golang.org"},
            },
        })
        return err
    })
    return http.ListenAndServeTLS(":https", "dmitri.shuralyov.com.pem", "dmitri.shuralyov.com-key.pem", h)
}

It's not quite static because it needs to serve multiple paths corresponding to package import paths, not just the root. But still very simple and doesn't need knowledge of what packages/modules exist or not. It just redirects everything, and the module mirror serves only the modules that do exist.