Capabilities now per-connection thing, new connection structure and proper MODE READER stub.
This commit is contained in:
parent
28ddc32368
commit
f5b1f9d9f9
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
// local
|
// local
|
||||||
"develop.pztrn.name/gonews/gonews/eventer"
|
"develop.pztrn.name/gonews/gonews/eventer"
|
||||||
"develop.pztrn.name/gonews/gonews/networker"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var capabilities = []string{
|
var capabilities = []string{
|
||||||
@ -16,13 +15,14 @@ var capabilities = []string{
|
|||||||
func Initialize() {
|
func Initialize() {
|
||||||
log.Println("Initializing capabilities command...")
|
log.Println("Initializing capabilities command...")
|
||||||
|
|
||||||
|
// Global capabilities adder.
|
||||||
eventer.AddEventHandler(&eventer.EventHandler{
|
eventer.AddEventHandler(&eventer.EventHandler{
|
||||||
Command: "internal/capability_add",
|
Command: "internal/capability_add",
|
||||||
Handler: addCapability,
|
Handler: addCapability,
|
||||||
})
|
})
|
||||||
|
|
||||||
eventer.AddEventHandler(&eventer.EventHandler{
|
eventer.AddEventHandler(&eventer.EventHandler{
|
||||||
Command: "commands/capabilities",
|
Command: "internal/capabilities",
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -33,12 +33,18 @@ func addCapability(data interface{}) interface{} {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handler(data interface{}) interface{} {
|
//func handler(data interface{}) interface{} {
|
||||||
dataToReturn := "Capability list:\r\n"
|
// dataToReturn := "Capability list:\r\n"
|
||||||
|
//
|
||||||
|
// for _, cap := range capabilities {
|
||||||
|
// dataToReturn += cap + "\r\n"
|
||||||
|
// }
|
||||||
|
// dataToReturn += ".\r\n"
|
||||||
|
// return &networker.Reply{Code: "101", Data: dataToReturn}
|
||||||
|
//}
|
||||||
|
|
||||||
for _, cap := range capabilities {
|
func handler(data interface{}) interface{} {
|
||||||
dataToReturn += cap + "\r\n"
|
caps := make([]string, len(capabilities))
|
||||||
}
|
copy(caps, capabilities)
|
||||||
dataToReturn += ".\r\n"
|
return caps
|
||||||
return &networker.Reply{Code: "101", Data: dataToReturn}
|
|
||||||
}
|
}
|
||||||
|
@ -11,34 +11,73 @@ import (
|
|||||||
"develop.pztrn.name/gonews/gonews/eventer"
|
"develop.pztrn.name/gonews/gonews/eventer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This function is a connection worker.
|
// This structure represents single NNTP client connection.
|
||||||
func connectionWorker(conn net.Conn) {
|
type connection struct {
|
||||||
remoteAddr := conn.RemoteAddr()
|
// Connection details and handlers.
|
||||||
|
conn net.Conn
|
||||||
|
remoteAddr net.Addr
|
||||||
|
|
||||||
|
// Read and write buffers
|
||||||
|
reader *bufio.Reader
|
||||||
|
writer *bufio.Writer
|
||||||
|
// Scanner that helps us to read incoming data.
|
||||||
|
readScanner *bufio.Scanner
|
||||||
|
|
||||||
|
// Connection flags.
|
||||||
|
// Are we in READER or MODE-READER (transit) mode?
|
||||||
|
// Right now transit mode isn't implemented. Implementation will
|
||||||
|
// require start using two goroutines for handling connections,
|
||||||
|
// one for writing and one for reading.
|
||||||
|
transit bool
|
||||||
|
// Connection capabilites.
|
||||||
|
capabilities []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes necessary things.
|
||||||
|
func (c *connection) Initialize(conn net.Conn) {
|
||||||
|
c.conn = conn
|
||||||
|
c.remoteAddr = c.conn.RemoteAddr()
|
||||||
|
|
||||||
log.Printf("accepted connection from %v\n", conn.RemoteAddr())
|
log.Printf("accepted connection from %v\n", conn.RemoteAddr())
|
||||||
|
|
||||||
defer func() {
|
|
||||||
err := conn.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Failed to close connection from " + remoteAddr.String() + ": " + err.Error())
|
|
||||||
}
|
|
||||||
log.Println("Connection from " + remoteAddr.String() + " closed")
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Create buffers.
|
// Create buffers.
|
||||||
r := bufio.NewReader(conn)
|
c.reader = bufio.NewReader(conn)
|
||||||
w := bufio.NewWriter(conn)
|
c.writer = bufio.NewWriter(conn)
|
||||||
scanr := bufio.NewScanner(r)
|
c.readScanner = bufio.NewScanner(c.reader)
|
||||||
|
|
||||||
|
// Get capabilities for this connection.
|
||||||
|
caps, _ := eventer.LaunchEvent("internal/capabilities", nil)
|
||||||
|
c.capabilities = caps.([]string)
|
||||||
|
|
||||||
|
// Set transit mode by default, according to RFC.
|
||||||
|
c.transit = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts working with connection. Should be launched in separate
|
||||||
|
// goroutine.
|
||||||
|
// It will send greeting and then falls into infinite loop for working
|
||||||
|
// with connection until the end.
|
||||||
|
// Right now it implements only READER mode, no transit (which is used
|
||||||
|
// by server-to-server peering extensively).
|
||||||
|
func (c *connection) Start() {
|
||||||
|
defer func() {
|
||||||
|
err := c.conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to close connection from " + c.remoteAddr.String() + ": " + err.Error())
|
||||||
|
}
|
||||||
|
log.Println("Connection from " + c.remoteAddr.String() + " closed")
|
||||||
|
}()
|
||||||
|
|
||||||
// Send greeting.
|
// Send greeting.
|
||||||
greetingData, _ := eventer.LaunchEvent("internal/greeting", nil)
|
greetingData, _ := eventer.LaunchEvent("internal/greeting", nil)
|
||||||
greetingReply := greetingData.(*Reply)
|
greetingReply := greetingData.(*Reply)
|
||||||
|
|
||||||
_, err := w.WriteString(greetingReply.Code + " " + greetingReply.Data)
|
_, err := c.writer.WriteString(greetingReply.Code + " " + greetingReply.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to write greeting for " + remoteAddr.String() + ": " + err.Error())
|
log.Println("Failed to write greeting for " + c.remoteAddr.String() + ": " + err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Flush()
|
c.writer.Flush()
|
||||||
|
|
||||||
// Start reading for commands.
|
// Start reading for commands.
|
||||||
// Every command can be represented as slice where first element
|
// Every command can be represented as slice where first element
|
||||||
@ -46,48 +85,87 @@ func connectionWorker(conn net.Conn) {
|
|||||||
// By default we read only one line per iteration.
|
// By default we read only one line per iteration.
|
||||||
// ToDo: multiline data parser for posting.
|
// ToDo: multiline data parser for posting.
|
||||||
for {
|
for {
|
||||||
dataAppeared := scanr.Scan()
|
dataAppeared := c.readScanner.Scan()
|
||||||
if !dataAppeared {
|
if !dataAppeared {
|
||||||
log.Println("Failed to read data from " + remoteAddr.String() + ": " + scanr.Err().Error())
|
log.Println("Failed to read data from " + c.remoteAddr.String() + ": " + c.readScanner.Err().Error())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Got data: " + scanr.Text())
|
log.Println("Got data: " + c.readScanner.Text())
|
||||||
|
|
||||||
// ToDo: what if we'll upload binary data here?
|
// ToDo: what if we'll upload binary data here?
|
||||||
// Not supported yet.
|
// Not supported yet.
|
||||||
data := strings.Split(scanr.Text(), " ")
|
data := strings.Split(c.readScanner.Text(), " ")
|
||||||
|
|
||||||
|
// Separate capabilities worker.
|
||||||
|
if strings.ToLower(data[0]) == "capabilities" {
|
||||||
|
dataToWrite := "Capability list:\r\n"
|
||||||
|
|
||||||
|
for _, cap := range c.capabilities {
|
||||||
|
dataToWrite += cap + "\r\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're also mode-switching server (in future), so we should
|
||||||
|
// also be aware of mode reader things. Writing to client will
|
||||||
|
// depend on c.transit variable.
|
||||||
|
// We will announce MODE-READER capability after initial
|
||||||
|
// connection (because we're in transit mode by default, according
|
||||||
|
// to RFC), and when client issue "MODE READER" command we will
|
||||||
|
// stop announcing MODE-READER and will start announce READER
|
||||||
|
// capability.
|
||||||
|
if c.transit {
|
||||||
|
dataToWrite += "MODE-READER\r\n"
|
||||||
|
} else {
|
||||||
|
dataToWrite += "READER\r\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
dataToWrite += ".\r\n"
|
||||||
|
c.writer.WriteString(dataToWrite)
|
||||||
|
c.writer.Flush()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode worker. Reader only now.
|
||||||
|
if strings.ToLower(data[0]) == "mode" && strings.ToLower(data[1]) == "reader" {
|
||||||
|
c.transit = false
|
||||||
|
// In any case we'll require user authentication for posting.
|
||||||
|
c.writer.WriteString("201 Posting prohibited\r\n")
|
||||||
|
c.writer.Flush()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute passed command.
|
||||||
replyRaw, err := eventer.LaunchEvent("commands/"+strings.ToLower(data[0]), data[1:])
|
replyRaw, err := eventer.LaunchEvent("commands/"+strings.ToLower(data[0]), data[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We won't break here as this is just logging of appeared error.
|
// We won't break here as this is just logging of appeared error.
|
||||||
log.Println("Error appeared while processing command '" + data[0] + "' for " + remoteAddr.String() + ": " + err.Error())
|
log.Println("Error appeared while processing command '" + data[0] + "' for " + c.remoteAddr.String() + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// We might have nil in reply, so we'll assume that passed command
|
// We might have nil in reply, so we'll assume that passed command
|
||||||
// is unknown to us.
|
// is unknown to us.
|
||||||
if replyRaw == nil {
|
if replyRaw == nil {
|
||||||
_, err := w.WriteString(unknownCommandErrorCode + " " + unknownCommandErrorText + "\r\n")
|
_, err := c.writer.WriteString(unknownCommandErrorCode + " " + unknownCommandErrorText + "\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to write string to socket for " + remoteAddr.String() + ": " + err.Error())
|
log.Println("Failed to write string to socket for " + c.remoteAddr.String() + ": " + err.Error())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
w.Flush()
|
c.writer.Flush()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every reply will be a reply struct.
|
// Every reply will be a reply struct.
|
||||||
reply := replyRaw.(*Reply)
|
reply := replyRaw.(*Reply)
|
||||||
|
|
||||||
_, err1 := w.WriteString(reply.Code + " " + reply.Data)
|
_, err1 := c.writer.WriteString(reply.Code + " " + reply.Data)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
log.Println("Failed to write string to socket for " + remoteAddr.String() + ": " + err1.Error())
|
log.Println("Failed to write string to socket for " + c.remoteAddr.String() + ": " + err1.Error())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
w.Flush()
|
c.writer.Flush()
|
||||||
|
|
||||||
// Check for QUIT command.
|
// Check for QUIT command.
|
||||||
if strings.ToLower(data[0]) == "quit" {
|
if strings.ToLower(data[0]) == "quit" {
|
||||||
log.Println("QUIT command received, closing connection to " + remoteAddr.String())
|
log.Println("QUIT command received, closing connection to " + c.remoteAddr.String())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,17 @@ import (
|
|||||||
"develop.pztrn.name/gonews/gonews/configuration"
|
"develop.pztrn.name/gonews/gonews/configuration"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var connections map[string]*connection
|
||||||
|
|
||||||
// This function responsible for accepting incoming connections for
|
// This function responsible for accepting incoming connections for
|
||||||
// each address configuration.
|
// each address configuration.
|
||||||
func startServer(config configuration.Network) {
|
func startServer(config configuration.Network) {
|
||||||
log.Println("Starting server on " + config.Address + " (type: " + config.Type + ")")
|
log.Println("Starting server on " + config.Address + " (type: " + config.Type + ")")
|
||||||
|
|
||||||
|
if connections == nil {
|
||||||
|
connections = make(map[string]*connection)
|
||||||
|
}
|
||||||
|
|
||||||
l, err := net.Listen("tcp", config.Address)
|
l, err := net.Listen("tcp", config.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to start TCP server on " + config.Address + ": " + err.Error())
|
log.Fatalln("Failed to start TCP server on " + config.Address + ": " + err.Error())
|
||||||
@ -32,6 +38,10 @@ func startServer(config configuration.Network) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
go connectionWorker(conn)
|
c := &connection{}
|
||||||
|
c.Initialize(conn)
|
||||||
|
connections[conn.RemoteAddr().String()] = c
|
||||||
|
|
||||||
|
go c.Start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user