diff --git a/cmd/example_httpu_serving/example_httpu_serving.go b/cmd/example_httpu_serving/example_httpu_serving.go new file mode 100644 index 0000000..93f57a6 --- /dev/null +++ b/cmd/example_httpu_serving/example_httpu_serving.go @@ -0,0 +1,25 @@ +package main + +import ( + "log" + "net" + "net/http" + + "github.com/huin/goupnp/httpu" +) + +func main() { + eth0, err := net.InterfaceByName("eth0") + if err != nil { + log.Fatal(err) + } + srv := httpu.Server{ + Addr: "239.255.255.250:1900", + Interface: eth0, + Handler: httpu.HandlerFunc(func(r *http.Request) { + log.Printf("Got %s %s message from %v: %v", r.Method, r.URL.Path, r.RemoteAddr, r.Header) + }), + } + err = srv.ListenAndServe() + log.Printf("Serving failed with error: %v", err) +} diff --git a/httpu/serve.go b/httpu/serve.go new file mode 100644 index 0000000..8972b17 --- /dev/null +++ b/httpu/serve.go @@ -0,0 +1,106 @@ +package httpu + +import ( + "bufio" + "bytes" + "log" + "net" + "net/http" + "regexp" +) + +const ( + DefaultMaxMessageBytes = 2048 +) + +var ( + trailingWhitespaceRx = regexp.MustCompile(" +\r\n") + crlf = []byte("\r\n") +) + +// Handler is the interface by which received HTTPU messages are passed to +// handling code. +type Handler interface { + // ServeMessage is called for each HTTPU message received. peerAddr contains + // the address that the message was received from. + ServeMessage(r *http.Request) +} + +// HandlerFunc is a function-to-Handler adapter. +type HandlerFunc func(r *http.Request) + +func (f HandlerFunc) ServeMessage(r *http.Request) { + f(r) +} + +// A Server defines parameters for running an HTTPU server. +type Server struct { + Addr string // UDP address to listen on + Interface *net.Interface // Network interface to listen on + Handler Handler // handler to invoke + MaxMessageBytes int // maximum number of bytes to read from a packet, DefaultMaxMessageBytes if 0 +} + +// ListenAndServe listens on the UDP network address srv.Addr. If srv.Interface +// != nil, then a multicast UDP listener will be used on the given interface. +func (srv *Server) ListenAndServe() error { + var err error + + var addr *net.UDPAddr + if addr, err = net.ResolveUDPAddr("udp", srv.Addr); err != nil { + log.Fatal(err) + } + + var conn net.PacketConn + if srv.Interface != nil { + if conn, err = net.ListenMulticastUDP("udp", srv.Interface, addr); err != nil { + return err + } + } else { + if conn, err = net.ListenUDP("udp", addr); err != nil { + return err + } + } + + return srv.Serve(conn) +} + +// Serve messages received on the given packet listener to the srv.Handler. +func (srv *Server) Serve(l net.PacketConn) error { + maxMessageBytes := DefaultMaxMessageBytes + if srv.MaxMessageBytes != 0 { + maxMessageBytes = srv.MaxMessageBytes + } + for { + buf := make([]byte, maxMessageBytes) + n, peerAddr, err := l.ReadFrom(buf) + if err != nil { + return err + } + buf = buf[:n] + + go func(buf []byte, peerAddr net.Addr) { + // At least one router's UPnP implementation has added a trailing space + // after "HTTP/1.1" - trim it. + buf = trailingWhitespaceRx.ReplaceAllLiteral(buf, crlf) + + req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(buf))) + if err != nil { + log.Printf("httpu: Failed to parse request: %v", err) + return + } + req.RemoteAddr = peerAddr.String() + srv.Handler.ServeMessage(req) + // No need to call req.Body.Close - underlying reader is bytes.Buffer. + }(buf, peerAddr) + } +} + +// Serve messages received on the given packet listener to the given handler. +func Serve(l net.PacketConn, handler Handler) error { + srv := Server{ + Handler: handler, + MaxMessageBytes: DefaultMaxMessageBytes, + } + return srv.Serve(l) +}