viewproxy

package module
v0.0.0-...-4c9e79b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 17, 2023 License: MIT Imports: 20 Imported by: 0

README

viewproxy

viewproxy is a Go service that makes multiple requests to an application in parallel, fetching HTML content and stitching it together to serve to a user.

This is alpha software, and is currently a proof of concept used in conjunction with Rails and View Component as a performance optimization.

Usage

See cmd/demo/main.go for an example of how to use the package.

To use viewproxy:

import "github.com/blakewilliams/viewproxy"
import "github.com/blakewilliams/viewproxy/pkg/fragment"

// Create and configure a new Server Instance
server := viewproxy.NewServer(target)
server.Port = 3005
server.ProxyTimeout = time.Duration(5) * time.Second
server.PassThrough = true

// Define a route with a :name parameter that will be forwarded to the target host.
// This will make a layout request and 3 fragment requests, one for the header, hello, and footer.

// GET http://localhost:3000/_view_fragments/layouts/my_layout?name=world
myPage := fragment.Define("my_layout", fragment.WithChildren(fragment.Children{
	"header": fragment.Define("header"), // GET http://localhost:3000/_view_fragments/header?name=world
	"hello": fragment.Define("hello"),  // GET http://localhost:3000/_view_fragments/hello?name=world
	"footer" fragment.Define("footer"), // GET http://localhost:3000/_view_fragments/footer?name=world
}))
server.Get("/hello/:name", myPage)

server.ListenAndServe()

Each child fragment is replaced in the parent fragment via a special tag, <viewproxy-fragment>. For example, the header fragment will be inserted into the my_layout fragment by looking for the following content: <viewproxy-fragment id="header"></viewproxy-fragment>.

Demo Usage

  • The port the server is bound to 3005 by default but can be set via the PORT environment variable.
  • The target server can be set via the TARGET environment variable.
    • The default is localhost:3000/_view_fragments
    • viewproxy will call that end-point with the fragment name being passed as a query parameter. e.g. localhost:3000/_view_fragments?fragment=header

To run viewproxy, run go build ./cmd/demo && ./demo

Tracing with Open Telemetry

You can use tracing to learn which fragment(s) are slowest for a given page, so you know where to optimize.

To set up distributed tracing via Open Telemetry, configure a tracing provider in your application that uses viewproxy, and viewproxy will use the default trace provider to create spans.

Tracing attributes via fragment metadata

Each fragment can be configured with a static map of key/values, which will be set as tracing attributes when each fragment is fetched.

layout := fragment.Define("my_layout")
server.Get("/hello/:name", layout, fragment.Collection{
	fragment.Define("header", fragment.WithMetadata(map[string]string{"page": "homepage"})), // spans will have a "page" attribute with value "homepage"
})

Philosophy

viewproxy is a simple service designed to sit between a browser request and a web application. It is used to break pages down into fragments that can be rendered in parallel for faster response times.

  • viewproxy is not coupled to a specific application framework, but is being driven by close integration with Rails applications.
  • viewproxy should rely on Rails' (or other target application framework) strengths when possible.
  • viewproxy itself and its client API's should focus on developer happiness and productivity.

Development

Run the tests:

go test ./...

Documentation

Index

Constants

View Source
const (
	HeaderViewProxyOriginalPath = "X-Viewproxy-Original-Path"
)

Variables

This section is empty.

Functions

func FragmentRouteFromContext

func FragmentRouteFromContext(ctx context.Context) *fragment.Definition

func ParametersFromContext

func ParametersFromContext(ctx context.Context) map[string]string

Types

type GetOption

type GetOption = func(*Route)

func WithRouteMetadata

func WithRouteMetadata(metadata map[string]string) GetOption

type ResultError

type ResultError = multiplexer.ResultError

Re-export ResultError for convenience

type Route

type Route struct {
	Path  string
	Parts []string

	RootFragment *fragment.Definition
	Metadata     map[string]string
	// contains filtered or unexported fields
}

func RouteFromContext

func RouteFromContext(ctx context.Context) *Route

func (*Route) FragmentOrder

func (r *Route) FragmentOrder() []string

func (*Route) FragmentsToRequest

func (r *Route) FragmentsToRequest() []*fragment.Definition

func (*Route) Validate

func (r *Route) Validate() error

Validates if the route and fragments have compatible dynamic route parts.

type RouteValidationError

type RouteValidationError struct {
	Route    *Route
	Fragment *fragment.Definition
}

func (*RouteValidationError) Error

func (rve *RouteValidationError) Error() string

type Server

type Server struct {
	Addr string
	// Sets the maximum duration for requests made to the target server
	ProxyTimeout time.Duration
	// Sets the maximum duration for reading the entire request, including the body
	ReadTimeout time.Duration
	// Sets the maximum duration before timing out writes of the response
	WriteTimeout time.Duration
	// Ignores incoming request's trailing slashes when trying to match a
	// request URL to a route. This only applies to routes that are not declared
	// with an explicit trailing slash.
	IgnoreTrailingSlash bool

	Logger logger

	SecretFilter secretfilter.Filter
	// Sets the secret used to generate an HMAC that can be used by the target
	// server to validate that a request came from viewproxy.
	//
	// When set, two headers are sent to the target URL for fragment and layout
	// requests. The `X-Authorization-Timestamp` header, which is a timestamp
	// generated at the start of the request, and `X-Authorization`, which is a
	// hex encoded HMAC of "urlPathWithQueryParams,timestamp`.
	HmacSecret string
	// The transport passed to `http.Client` when fetching fragments or proxying
	// requests.
	// HttpTransport      http.RoundTripper
	MultiplexerTripper multiplexer.Tripper
	// A function to wrap the entire request handling with other middleware
	AroundRequest func(http.Handler) http.Handler
	// A function to wrap around the generating of the response after the fragment
	// requests have completed or errored
	AroundResponse func(http.Handler) http.Handler
	// contains filtered or unexported fields
}

func NewServer

func NewServer(target string, opts ...ServerOption) (*Server, error)

NewServer returns a new Server that will make requests to the given target argument.

func (*Server) Close

func (s *Server) Close()

func (*Server) CreateHandler

func (s *Server) CreateHandler() http.Handler

func (*Server) Get

func (s *Server) Get(path string, root *fragment.Definition, opts ...GetOption) error

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

func (*Server) MatchingRoute

func (s *Server) MatchingRoute(path string) (*Route, map[string]string)

TODO this should probably be a tree structure for faster lookups

func (*Server) PassThroughEnabled

func (s *Server) PassThroughEnabled() bool

func (*Server) Routes

func (s *Server) Routes() []Route

routes returns a slice containing routes defined on the server.

func (*Server) Serve

func (s *Server) Serve(listener net.Listener) error

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

func (*Server) Target

func (s *Server) Target() string

target returns the configured http target

type ServerOption

type ServerOption = func(*Server) error

func WithPassThrough

func WithPassThrough(passthroughTarget string) ServerOption

Directories

Path Synopsis
cmd
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL