mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
Allow for UDP servers (#935)
* Allow for UDP servers Extend the Server interface with ServePacket and ListenPacket - this is in the same vein as the net package. Plumb the packetconn through the start and restart phases. Rename RestartPair to RestartTriple as it now also contains a Packet. Not that these can now be nil, so we need to check for that when restarting. * Update the documentation
This commit is contained in:
parent
502a8979a8
commit
9315738dab
3 changed files with 98 additions and 44 deletions
133
caddy.go
133
caddy.go
|
@ -148,12 +148,24 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add file descriptors of all the sockets that are capable of it
|
// Add file descriptors of all the sockets that are capable of it
|
||||||
restartFds := make(map[string]restartPair)
|
restartFds := make(map[string]restartTriple)
|
||||||
for _, s := range i.servers {
|
for _, s := range i.servers {
|
||||||
gs, srvOk := s.server.(GracefulServer)
|
gs, srvOk := s.server.(GracefulServer)
|
||||||
ln, lnOk := s.listener.(Listener)
|
ln, lnOk := s.listener.(Listener)
|
||||||
if srvOk && lnOk {
|
pc, pcOk := s.packet.(PacketConn)
|
||||||
restartFds[gs.Address()] = restartPair{server: gs, listener: ln}
|
if srvOk {
|
||||||
|
if lnOk && pcOk {
|
||||||
|
restartFds[gs.Address()] = restartTriple{server: gs, listener: ln, packet: pc}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if lnOk {
|
||||||
|
restartFds[gs.Address()] = restartTriple{server: gs, listener: ln}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pcOk {
|
||||||
|
restartFds[gs.Address()] = restartTriple{server: gs, packet: pc}
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,42 +235,38 @@ func listenerAddrEqual(ln net.Listener, addr string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: We should be able to support UDP servers... I'm considering this pattern.
|
|
||||||
|
|
||||||
type UDPListener struct {
|
|
||||||
*net.UDPConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UDPListener) Accept() (net.Conn, error) {
|
|
||||||
return u.UDPConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UDPListener) Close() error {
|
|
||||||
return u.UDPConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UDPListener) Addr() net.Addr {
|
|
||||||
return u.UDPConn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Listener = UDPListener{}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Server is a type that can listen and serve. A Server
|
// Server is a type that can listen and serve. A Server
|
||||||
// must associate with exactly zero or one listeners.
|
// must associate with exactly zero or one listeners and packetconns.
|
||||||
|
// The listed method "pair up", Listen() and Serve() are needed for a
|
||||||
|
// TCP server and ListenPacket() and ServePacket() are needed for a
|
||||||
|
// UDP server. Do both TCP and UDP means all four are needed.
|
||||||
type Server interface {
|
type Server interface {
|
||||||
// Listen starts listening by creating a new listener
|
// Listen starts listening by creating a new listener
|
||||||
// and returning it. It does not start accepting
|
// and returning it. It does not start accepting
|
||||||
// connections.
|
// connections.
|
||||||
Listen() (net.Listener, error)
|
Listen() (net.Listener, error)
|
||||||
|
|
||||||
|
// ListenPacket starts listening by creating a new packetconn
|
||||||
|
// and returning it. It does not start accepting connections.
|
||||||
|
// For a TCP only server this method can be a noop and just
|
||||||
|
// return (nil, nil).
|
||||||
|
ListenPacket() (net.PacketConn, error)
|
||||||
|
|
||||||
// Serve starts serving using the provided listener.
|
// Serve starts serving using the provided listener.
|
||||||
// Serve must start the server loop nearly immediately,
|
// Serve must start the server loop nearly immediately,
|
||||||
// or at least not return any errors before the server
|
// or at least not return any errors before the server
|
||||||
// loop begins. Serve blocks indefinitely, or in other
|
// loop begins. Serve blocks indefinitely, or in other
|
||||||
// words, until the server is stopped.
|
// words, until the server is stopped.
|
||||||
Serve(net.Listener) error
|
Serve(net.Listener) error
|
||||||
|
|
||||||
|
// ServePacket starts serving using the provided packetconn.
|
||||||
|
// ServePacket must start the server loop nearly immediately,
|
||||||
|
// or at least not return any errors before the server
|
||||||
|
// loop begins. ServePacket blocks indefinitely, or in other
|
||||||
|
// words, until the server is stopped.
|
||||||
|
// For a TCP only server this method can be a noop and just
|
||||||
|
// return nil.
|
||||||
|
ServePacket(net.PacketConn) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stopper is a type that can stop serving. The stop
|
// Stopper is a type that can stop serving. The stop
|
||||||
|
@ -299,6 +307,15 @@ type Listener interface {
|
||||||
File() (*os.File, error)
|
File() (*os.File, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PacketConn is a net.PacketConn with an underlying file descriptor.
|
||||||
|
// A server's packetconn should implement this interface if it is
|
||||||
|
// to support zero-downtime reloads (in sofar this holds true for datagram
|
||||||
|
// connections).
|
||||||
|
type PacketConn interface {
|
||||||
|
net.PacketConn
|
||||||
|
File() (*os.File, error)
|
||||||
|
}
|
||||||
|
|
||||||
// AfterStartup is an interface that can be implemented
|
// AfterStartup is an interface that can be implemented
|
||||||
// by a server type that wants to run some code after all
|
// by a server type that wants to run some code after all
|
||||||
// servers for the same Instance have started.
|
// servers for the same Instance have started.
|
||||||
|
@ -384,7 +401,7 @@ func Start(cdyfile Input) (*Instance, error) {
|
||||||
return inst, startWithListenerFds(cdyfile, inst, nil)
|
return inst, startWithListenerFds(cdyfile, inst, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartPair) error {
|
func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple) error {
|
||||||
if cdyfile == nil {
|
if cdyfile == nil {
|
||||||
cdyfile = CaddyfileInput{}
|
cdyfile = CaddyfileInput{}
|
||||||
}
|
}
|
||||||
|
@ -535,27 +552,45 @@ func executeDirectives(inst *Instance, filename string,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startServers(serverList []Server, inst *Instance, restartFds map[string]restartPair) error {
|
func startServers(serverList []Server, inst *Instance, restartFds map[string]restartTriple) error {
|
||||||
errChan := make(chan error, len(serverList))
|
errChan := make(chan error, len(serverList))
|
||||||
|
|
||||||
for _, s := range serverList {
|
for _, s := range serverList {
|
||||||
var ln net.Listener
|
var (
|
||||||
var err error
|
ln net.Listener
|
||||||
|
pc net.PacketConn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
// If this is a reload and s is a GracefulServer,
|
// If this is a reload and s is a GracefulServer,
|
||||||
// reuse the listener for a graceful restart.
|
// reuse the listener for a graceful restart.
|
||||||
if gs, ok := s.(GracefulServer); ok && restartFds != nil {
|
if gs, ok := s.(GracefulServer); ok && restartFds != nil {
|
||||||
addr := gs.Address()
|
addr := gs.Address()
|
||||||
if old, ok := restartFds[addr]; ok {
|
if old, ok := restartFds[addr]; ok {
|
||||||
file, err := old.listener.File()
|
// listener
|
||||||
if err != nil {
|
if old.listener != nil {
|
||||||
return err
|
file, err := old.listener.File()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ln, err = net.FileListener(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
}
|
}
|
||||||
ln, err = net.FileListener(file)
|
// packetconn
|
||||||
if err != nil {
|
if old.packet != nil {
|
||||||
return err
|
file, err := old.packet.File()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pc, err = net.FilePacketConn(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
}
|
}
|
||||||
file.Close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,14 +600,25 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pc == nil {
|
||||||
|
pc, err = s.ListenPacket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inst.wg.Add(1)
|
inst.wg.Add(2)
|
||||||
go func(s Server, ln net.Listener, inst *Instance) {
|
go func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {
|
||||||
defer inst.wg.Done()
|
defer inst.wg.Done()
|
||||||
errChan <- s.Serve(ln)
|
|
||||||
}(s, ln, inst)
|
|
||||||
|
|
||||||
inst.servers = append(inst.servers, serverListener{server: s, listener: ln})
|
go func() {
|
||||||
|
errChan <- s.Serve(ln)
|
||||||
|
defer inst.wg.Done()
|
||||||
|
}()
|
||||||
|
errChan <- s.ServePacket(pc)
|
||||||
|
}(s, ln, pc, inst)
|
||||||
|
|
||||||
|
inst.servers = append(inst.servers, serverListener{server: s, listener: ln, packet: pc})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log errors that may be returned from Serve() calls,
|
// Log errors that may be returned from Serve() calls,
|
||||||
|
@ -752,9 +798,10 @@ func writePidFile() error {
|
||||||
return ioutil.WriteFile(PidFile, pid, 0644)
|
return ioutil.WriteFile(PidFile, pid, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
type restartPair struct {
|
type restartTriple struct {
|
||||||
server GracefulServer
|
server GracefulServer
|
||||||
listener Listener
|
listener Listener
|
||||||
|
packet PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -146,6 +146,9 @@ func (s *Server) Listen() (net.Listener, error) {
|
||||||
return ln.(*net.TCPListener), nil
|
return ln.(*net.TCPListener), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListenPacket is a noop to implement the Server interface.
|
||||||
|
func (s *Server) ListenPacket() (net.PacketConn, error) { return nil, nil }
|
||||||
|
|
||||||
// Serve serves requests on ln. It blocks until ln is closed.
|
// Serve serves requests on ln. It blocks until ln is closed.
|
||||||
func (s *Server) Serve(ln net.Listener) error {
|
func (s *Server) Serve(ln net.Listener) error {
|
||||||
if tcpLn, ok := ln.(*net.TCPListener); ok {
|
if tcpLn, ok := ln.(*net.TCPListener); ok {
|
||||||
|
@ -186,6 +189,9 @@ func (s *Server) Serve(ln net.Listener) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServePacket is a noop to implement the Server interface.
|
||||||
|
func (s *Server) ServePacket(pc net.PacketConn) error { return nil }
|
||||||
|
|
||||||
// ServeHTTP is the entry point of all HTTP requests.
|
// ServeHTTP is the entry point of all HTTP requests.
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
|
@ -82,10 +82,11 @@ func ValidDirectives(serverType string) []string {
|
||||||
return stype.Directives
|
return stype.Directives
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverListener pairs a server to its listener.
|
// serverListener pairs a server to its listener and/or packetconn.
|
||||||
type serverListener struct {
|
type serverListener struct {
|
||||||
server Server
|
server Server
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
|
packet net.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context is a type which carries a server type through
|
// Context is a type which carries a server type through
|
||||||
|
|
Loading…
Reference in a new issue