-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi-Listener? (Wrap Caddy Listener vs. Ignore Caddy Listener) #19
Comments
caddy-ngrok
plugin started last month but was struggling to get the foundations together.
Previous to having this wrapper I stood up an ngrok agent in a docker container and pointed it at my caddy container. I still exposed my caddy port locally and would access the port on my local network when I didn't want to go out to the internet. That would be my usecase. But after further thought I can just do the following couldn't I?
If that's the case then caddy already provides the solution to this and we likely don't need to do this. |
That works too. However, I've been doing some thinking. Given the module is within We can extend this repo by adding another module that makes use of Caddy's custom network registration to obtain only an ngrok listener for the given address. The address in this case an indicator of which ngrok tunnel to use. The code for that is as so: package ngroklistener
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/caddyserver/caddy/v2"
"go.uber.org/zap"
"golang.ngrok.com/ngrok"
ngrokZap "golang.ngrok.com/ngrok/log/zap"
)
func init() {
caddy.RegisterNetwork("ngrok", func(ctx context.Context, _, addr string, _ net.ListenConfig) (any, error) {
app, err := caddy.ActiveContext().App("ngrok")
if err != nil {
return nil, err
}
var ngrokApp *App
var ok bool
ngrokApp, ok = app.(*App)
if !ok || app == nil {
panic("TODO")
}
tun, ok := ngrokApp.tunnels[addr]
if !ok {
panic("TODO")
}
return ngrok.Listen(
ctx,
tun.NgrokTunnel(),
ngrokApp.opts...,
)
})
caddy.RegisterModule(new(App))
}
type App struct {
// The user's ngrok authentication token
AuthToken string `json:"authtoken,omitempty"`
// a map of tunnels as identified by the given name that is the key
TunnelsRaw map[string]json.RawMessage `json:"tunnels,omitempty" caddy:"namespace=caddy.listeners.ngrok.tunnels inline_key=type"`
// Opaque, machine-readable metadata string for this session.
// Metadata is made available to you in the ngrok dashboard and the
// Agents API resource. It is a useful way to allow you to uniquely identify
// sessions. We suggest encoding the value in a structured format like JSON.
Metadata string `json:"metadata,omitempty"`
// Region configures the session to connect to a specific ngrok region.
// If unspecified, ngrok will connect to the fastest region, which is usually what you want.
// The [full list of ngrok regions] can be found in the ngrok documentation.
Region string `json:"region,omitempty"`
// Server configures the network address to dial to connect to the ngrok
// service. Use this option only if you are connecting to a custom agent
// ingress.
//
// See the [server_addr parameter in the ngrok docs] for additional details.
Server string `json:"server,omitempty"`
// HeartbeatTolerance configures the duration to wait for a response to a heartbeat
// before assuming the session connection is dead and attempting to reconnect.
//
// See the [heartbeat_tolerance parameter in the ngrok docs] for additional details.
HeartbeatTolerance caddy.Duration `json:"heartbeat_tolerance,omitempty"`
// HeartbeatInterval configures how often the session will send heartbeat
// messages to the ngrok service to check session liveness.
//
// See the [heartbeat_interval parameter in the ngrok docs] for additional details.
HeartbeatInterval caddy.Duration `json:"heartbeat_interval,omitempty"`
opts []ngrok.ConnectOption
tunnels map[string]Tunnel
ctx context.Context
l *zap.Logger
}
// CaddyModule implements caddy.Module
func (*App) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "ngrok",
New: func() caddy.Module {
return new(App)
},
}
}
// Provisions the ngrok listener wrapper
func (n *App) Provision(ctx caddy.Context) error {
n.ctx = ctx
n.l = ctx.Logger()
if n.TunnelsRaw == nil {
n.TunnelsRaw = map[string]json.RawMessage{
"default": json.RawMessage(`{"type": "tcp"}`),
}
}
tmod, err := ctx.LoadModule(n, "TunnelsRaw")
if err != nil {
return fmt.Errorf("loading ngrok tunnel module: %v", err)
}
for k, v := range tmod.(map[string]Tunnel) {
n.tunnels[k] = v
}
if err = n.doReplace(); err != nil {
return fmt.Errorf("loading doing replacements: %v", err)
}
if err = n.provisionOpts(); err != nil {
return fmt.Errorf("provisioning ngrok opts: %v", err)
}
return nil
}
func (n *App) provisionOpts() error {
n.opts = append(n.opts, ngrok.WithLogger(ngrokZap.NewLogger(n.l)))
if n.AuthToken == "" {
n.opts = append(n.opts, ngrok.WithAuthtokenFromEnv())
} else {
n.opts = append(n.opts, ngrok.WithAuthtoken(n.AuthToken))
}
if n.Metadata != "" {
n.opts = append(n.opts, ngrok.WithMetadata(n.Metadata))
}
if n.Region != "" {
n.opts = append(n.opts, ngrok.WithRegion(n.Region))
}
if n.Server != "" {
n.opts = append(n.opts, ngrok.WithServer(n.Server))
}
n.opts = append(n.opts, ngrok.WithHeartbeatInterval(time.Duration(n.HeartbeatInterval)))
n.opts = append(n.opts, ngrok.WithHeartbeatTolerance(time.Duration(n.HeartbeatTolerance)))
return nil
}
func (n *App) doReplace() error {
repl := caddy.NewReplacer()
replaceableFields := []*string{
&n.AuthToken,
&n.Metadata,
&n.Region,
&n.Server,
}
for _, field := range replaceableFields {
actual := repl.ReplaceKnown(*field, "")
*field = actual
}
return nil
}
// Start implements caddy.App
func (*App) Start() error {
return nil
}
// Stop implements caddy.App
func (*App) Stop() error {
return nil
}
var (
_ caddy.App = (*App)(nil)
_ caddy.Module = (*App)(nil)
) An example configuration for such is: {
"apps": {
"ngrok": {
"authtoken": "{env.NGROK_AUTH_TOKEN}",
"tunnels": {
"my-http-tunnel": {
"type": "http"
},
"labeled-tunnel-1": {
"type": "label"
},
"raw-tcp": {
"type": "tcp"
}
}
},
"http": {
"servers": {
"srv0": {
"listen": [
"ngrok/labeled-tunnel-1"
],
"routes": [
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile-2"
]
}
]
}
]
}
],
"terminal": true
}
]
},
"srv1": {
"listen": [
"ngrok/raw-tcp"
],
"routes": [
{
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"handler": "static_response",
"body": "Hello!"
}
]
}
]
}
],
"terminal": true
}
]
}
}
}
}
} What do you think? |
[@jtszalay copying and moving to an issue for easier tracking 🙂]
Ignoring Caddy's listener just felt natural for the purpose of the module 😂
Intriguing! But I'm not sure whether users will naturally expect both listeners to work, because it's a "wrapper", or only ngrok's listener because it's ngrok. Do you think it's possible to have a toggle? You don't have to go the toggle route, I'm genuinely curious which way is better.
I don't personally use ngrok, even though I drafted this module, so I can't have a solid opinion on it. What do you think users expect?
Originally posted by @mohammed90 in #4 (comment)
The text was updated successfully, but these errors were encountered: