browser

The browser.example microservice is a one-page web “browser”: an address bar plus the raw source of whatever URL you submit. It demonstrates two related patterns:

  • Outbound HTTP through the HTTP egress core microservice. A Microbus microservice should not call http.Get directly — outbound HTTP runs through the egress proxy so it picks up the framework’s tracing, retries, time budgets, and (in test runs) a mock you can substitute for the real network.
  • A clean test seam. Because the call goes through the httpegressapi client, swapping in a mock egress service in tests is a one-line change. The example’s service_test.go exercises this.

Depends On

  • httpegress.core — the egress proxy that owns the actual outbound HTTP call.

Try It

With the examples app running, open the browser UI:

http://localhost:8080/browser.example/browse

Enter any URL in the address bar and submit. The HTML/text source of the response is rendered below it inside a <pre> block.

Code Walkthrough

Source: exampleservices/browser/service.go.

The Service struct is bare; no state is needed because every request is independent:

type Service struct {
    *Intermediate // IMPORTANT: Do not remove
}

OnStartup and OnShutdown are empty.

Wiring (intermediate.go)

A single web subscription from intermediate.go:

func NewService() *Service {
    svc := &Service{}
    svc.Intermediate = NewIntermediate(svc)
    return svc
}
svc.Subscribe(
    "Browse", svc.Browse,
    sub.At(browserapi.Browse.Method, browserapi.Browse.Route),
    sub.Description(`Browse shows a simple address bar and the source code of a URL.`),
    sub.Web(),
)

Browse

func (svc *Service) Browse(w http.ResponseWriter, r *http.Request) (err error) {
    ctx := r.Context()
    u := r.URL.Query().Get("url")
    if u != "" && !strings.Contains(u, "://") {
        u = "https://" + u
    }

    var page strings.Builder
    page.WriteString("<html><head>")
    page.WriteString("</head><body>")

    // Address bar and button
    page.WriteString(`<form method=GET action=browse>`)
    page.WriteString(`<input type=text name=url size=80 placeholder="Enter a URL" value="`)
    page.WriteString(html.EscapeString(u))
    page.WriteString(`">`)
    page.WriteString(`<input type=submit value="View Source">`)
    page.WriteString(`</form>`)

    // Source code
    if u != "" {
        resp, err := httpegressapi.NewClient(svc).Get(ctx, u)
        if err != nil {
            return errors.Trace(err)
        }
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            return errors.Trace(err)
        }
        page.WriteString(`<pre style="white-space:pre-wrap">`)
        page.WriteString(html.EscapeString(string(body)))
        page.WriteString("</pre>")
    }

    page.WriteString("</body></html>")

    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte(page.String()))
    return nil
}

The handler itself is mostly HTML-string assembly. The Microbus-relevant line is in the middle:

resp, err := httpegressapi.NewClient(svc).Get(ctx, u)

httpegressapi.NewClient(svc).Get(ctx, u) looks identical to a normal HTTP call but routes the request through the bus to the httpegress.core microservice, which performs the actual outbound call. From the perspective of the caller this is just another service-to-service call — same context propagation, same tracing, same time budget. In tests, httpegress is replaced with a mock that returns canned responses, so browser’s test never touches the real network.

The url input is wrapped with "https://" if no scheme is present so a user can type microbus.io and get the expected behavior. URL validation is left to the egress proxy; bad URLs surface as a normal error from Get and are returned to the caller.

See Also