Skip to content

Commit

Permalink
docs: update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
andydunstall committed Apr 21, 2024
1 parent 6803ce1 commit c860e9c
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 34 deletions.
34 changes: 12 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Pico

> :warning: Pico is still a proof of concept so is not yet suitable for
> :warning: Pico currently only a proof of concept so is not yet suitable for
production. See 'Limitations' below.

Pico is a reverse proxy that allows you to expose a service that isn't publicly
routable (known as tunnelling). Pico is designed to serve production traffic.
routable (known as tunnelling). Unlike other open-source tunelling solutions,
Pico is designed to serve production traffic.

Upstream services register a listener with Pico via an outbound-only
connection. Downstream clients may then send HTTP(S) requests to Pico which
Expand Down Expand Up @@ -39,7 +40,7 @@ Pico is designed to be simple to host, particularly in Kubernetes. Therefore
Pico may be hosted behind a HTTP load balancer or
[Kubernetes Gateway](https://kubernetes.io/docs/concepts/services-networking/gateway/).

The downside of this approach is it means the proxy only supports HTTP. Pico
The downside of this approach is it means Pico only supports HTTP. Pico
also uses WebSockets internally to communicate with upstream listeners, which
are typically supported by HTTP load balancers.

Expand All @@ -49,13 +50,16 @@ any static configuration. When multiple listeners register with the same
endpoint ID, Pico will load balance requests among those listeners.

## Components

### Server
The Pico server is responsible for proxying requests from downstream clients to
registered upstream listeners.

Upstreams register one or more listeners with the server via an outbound-only
connection. Each listener is identified by an endpoint ID.

Pico may be hosted as a cluster of servers for fault tolerance and scalability.

#### Routing
Incoming HTTP requests include the endpoint ID to route to in either the `Host`
header or an `x-pico-endpoint` header, then Pico load balances requests among
Expand All @@ -82,21 +86,18 @@ then forwards incoming requests to your upstream service.
Such as if you have a service running at `localhost:3000`, you can register
endpoint `my-endpoint` that forwards requests to that local service.

Alternatively you can use an SDK where you register listeners directly in your
application, rather than requiring an external process.

## Getting Started
This section describes how to run both the Pico server and agent locally. In
production you'd host the server remotely though this is still useful to demo
Pico.
production you'd host the server remotely as a cluster, though this is still
useful to demo Pico.

Start by either downloading the `pico` binary from the releases page, or to
build Pico directly you can clone the repo and run `make pico` (which requires
Go 1.21 or later).

### Server
Start the server with `pico server`, which will run at `localhost:8080` by
default.
Start the server with `pico server`, which will listen for proxy reuqests at
`localhost:8080` by default.

See `pico server -h` for the available configuration options.

Expand All @@ -106,7 +107,7 @@ Next start a service you would like to route requests to, such as
port `3000`.

Next you can start Pico agent with
`pico agent --listener my-endpoint-123/localhost:3000` which registers a
`pico agent --listeners my-endpoint-123/localhost:3000` which registers a
listener with endpoint ID `my-endpoint-123` and forwards requests to
`localhost:3000`.

Expand All @@ -128,14 +129,3 @@ See [docs](./docs) for details on deploying and managing Pico, plus details on
the Pico architecture:
- Deploy
- [Observability](./docs/deploy/observability.md)

## Limitations
> :warning: Pico is still a proof of concept so is not yet suitable for
production.

Pico does not yet support clustering or authentication. Currently working
on adding a gossip layer for endpoint discovery with
[kite](https://github.com/andydunstall/kite).

Pico also only supports using Pico agent to register listeners, though aiming
to add support for a Go SDK as well.
12 changes: 1 addition & 11 deletions docs/deploy/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,4 @@ overrides `--log.level` for the configured subsystems. Such as
list. Such as `gossip` will match `gossip` but not `gossip.kite`.

## Metrics
Both the Pico server and agent expose Prometheus metrics at /pico/v1/metrics.

### Available Metrics
| Metric | Type | Labels | Description |
| ---------------------------------- | --------- | ------------- | -------------------------------------------------- |
| proxy_requests_total | Counter | status | Proxied requests total |
| proxy_request_latency_seconds | Histogram | status | Proxied request latency histogram |
| proxy_errors_total | Counter | | Errors forwarding proxied requests |
| proxy_listeners | Gauge | | Number of registered upstream listeners |
| http_requests_total | Counter | status | HTTP requests total |
| http_request_latency_seconds | Histogram | status | HTTP request latency histogram |
The Pico server exposes Prometheus on the admin port at `/metrics`.
22 changes: 21 additions & 1 deletion server/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -88,7 +89,7 @@ func NewProxy(
}

func (p *Proxy) Request(ctx context.Context, r *http.Request) (*http.Response, error) {
endpointID := r.Header.Get("x-pico-endpoint")
endpointID := parseEndpointID(r)
if endpointID == "" {
p.logger.Warn(
"failed to proxy request: missing endpoint id",
Expand Down Expand Up @@ -323,3 +324,22 @@ func (p *Proxy) requestRemote(

return resp, nil
}

func parseEndpointID(r *http.Request) string {
endpointID := r.Header.Get("x-pico-endpoint")
if endpointID != "" {
return endpointID
}

host := r.Header.Get("host")
if host != "" && strings.Contains(host, ".") {
// If a host is given and contains a separator, use the bottom-level
// domain as the endpoint ID.
//
// Such as if the domain is 'xyz.pico.example.com', then 'xyz' is the
// endpoint ID.
return strings.Split(host, ".")[0]
}

return ""
}

0 comments on commit c860e9c

Please sign in to comment.