<commit_before>/* vim: set sw=4 sts=4 et foldmethod=syntax : */

/*
 * Copyright (c) 2011 Alexander Færøy <ahf@0x90.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
    "bufio"
    "fmt"
    "net"
    "strconv"
    "strings"
    "time"
)

type Client struct {
    input chan *Message // Parsed IRC messages.
    output chan string // Output.

    output_quit chan bool // Leave the Output Handler.
    protocol_handler_quit chan bool // Leave the (Un)registered protocol handler.

    nickname string // The Nickname of the client.
    username string // The Username of the client.
    realname string // The Realname/Gecos of the client.
    hostname string // The hostname of the client (after we have done a reverse DNS lookup).

    address string // The IP address of the client.
    port int // The port.

    connection net.Conn // The connection.
    readwriter *bufio.ReadWriter // The buffered reader/writer.
    listener Listener // The listener.

    ircd *Ircd // Pointer to our IRCd.

    channels *ChannelSet // Channels.

    timestamp int64 // Creation time in seconds since UNIX epoch.

    // Housekeeping (FIXME: Needs refactoring).
    isRegistered bool
}

func NewClient(listener Listener, ircd *Ircd, connection net.Conn, remoteAddr string) {
    client := new(Client)

    // The IRCd.
    client.ircd = ircd

    // Timestamp.
    client.timestamp = time.Seconds()

    // IRC Channels.
    client.channels = NewChannelSet()

    // Channels.
    client.input = make(chan *Message)
    client.output = make(chan string)

    // Quit Channels.  These are being used to signal to each of our client
    // processes that it's time to exit.
    client.output_quit = make(chan bool)
    client.protocol_handler_quit = make(chan bool)

    // RemoteAddr is on the form "IP:Port". Split it.
    address, portStr, error := net.SplitHostPort(remoteAddr)

    if error != nil {
        panic("Bug: Unable to split host/port.")
    }

    // Convert it to an integer.
    port, error := strconv.Atoi(portStr)

    if error != nil {
        panic("Bug: Unable to convert port to an int.")
    }

    // Set address and port.
    client.address = address
    client.port = port

    // Start background job for looking up the hostname.
    hostnameCh := LookupAddress(address)

    // Set initial values.
    client.nickname = "*"
    client.username = "*"
    client.realname = "*"

    // Use the IP for now.
    client.hostname = address

    // Set connection, listener and create readwriter.
    client.connection = connection
    client.listener = listener
    client.readwriter = bufio.NewReadWriter(bufio.NewReader(connection), bufio.NewWriter(connection))

    // Used for synchronisation once we are done looking up rDNS and do identd
    // check.
    sync := make(chan bool)

    // Socket Output Handler.
    go client.OutputHandler()

    // Socket Input Handler.
    go client.InputHandler()

    // Unregistered Protocol Handler.  The Unregistered Protocol Handler only
    // accepts either the NICK or USER command. Once the client has been
    // registered, the UnregisteredProtocolHandler() will die and the
    // RegisteredProtocolHandler() will take over.
    go client.UnregisteredProtocolHandler(sync)

    // Start sending IRC messages to the client.
    client.WriteStringF("NOTICE AUTH :*** Processing connection to %s ...", ircd.Me())
    client.WriteString("NOTICE AUTH :*** Looking up your hostname ...")

    // Get result from rDNS lookup.
    host := <-hostnameCh
    client.WriteStringF("NOTICE AUTH :*** Found your hostname (%s) ...", host)
    client.hostname = host

    // Let the UnregisteredProtocolHandler() know that it's okay to exit and
    // let the RegisteredProtocolHandler() take over now.
    sync<-true
}

func (this *Client) InputHandler() {
    this.Printf("Entering Socket Input Handler")
    defer this.Printf("Leaving Socket Input Handler")

    rw := this.readwriter

    for {
        // Read until a newline.
        s, error := rw.ReadString('\n')

        if error != nil {
            // Read Error (Usually happens when the connection is closed by the
            // client).
            // FIXME: Should probably check for non-EOF errors.
            this.Printf("Read Error: %s", error)

            // Start a chain of Quit-messages to the various client processes
            // to ensure that they are all shutdown gracefully. The goal of
            // this is to avoid any dangling processes.
            this.output_quit<-true

            // Exit.
            return
        }

        // Trim "\r\n".
        line := strings.TrimRight(s, "\r\n")

        // Skip "empty" lines.
        if len(Strip(line)) == 0 {
            continue
        }

        // Parse.
        message := Parse(line)

        // Most ASCII strings are valid IRC commands, but in the
        // unlikely case that Parse() returns nil, we'll simply skip
        // it.
        if message != nil {
            this.Printf("-> '%s'", message)

            // Pass on to the ProtocolHandler (Either Registered or Unregistered).
            this.input<-message
        }
    }
}

func (this *Client) OutputHandler() {
    this.Printf("Entering Socket Output Handler")
    defer this.Printf("Leaving Socket Output Handler")

    defer func () {
        this.Printf("Sending Quit-message to the Protocol Handler")
        this.protocol_handler_quit<-true
    }()

    rw := this.readwriter

    for {
        select {
            case <-this.output_quit:
                // We were told to exit. Notice the above defer statements
                // where we chain the quit message on to the message handler
                // process.
                return

            case s, ok := <-this.output:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Ignore the byte-count for now.  Go sets error to non-nil if
                // bytes != len(input).
                _, error := rw.WriteString(s + "\r\n")

                if error != nil {
                    this.Printf("Write Error: %s", error)

                    // Unlikely, but we'll quit.
                    return
                }

                this.Printf("<- '%s'", s)

                // Flush the buffer.
                rw.Flush()
        }
    }
}

func (this *Client) UnregisteredProtocolHandler(sync chan bool) {
    this.Printf("Entering Unregistered Client Protocol Handler")
    defer this.Printf("Leaving Unregistered Client Protocol Handler")

    // The IRCd.
    ircd := this.ircd

    // Housekeeping.
    got_nickname := false
    got_username := false

    // Temporaries.
    var nickname string
    var username string
    var realname string

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // This client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                cmd := message.Command()
                args := message.Arguments()

                switch cmd {
                    case "NICK":
                        // Missing nickname.
                        if len(args) < 1 {
                            this.SendNumeric(ERR_NONICKNAMEGIVEN, ircd.Me(), "*")
                            continue
                        }

                        n := args[0]

                        // Check if the nickname is valid.
                        if ! IsValidNickname(n) {
                            this.SendNumeric(ERR_ERRONEUSNICKNAME, ircd.Me(), "*", n)
                            continue
                        }

                        // Check if the nickname is already registered.
                        if this.ircd.FindClient(n) != nil {
                            this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", n)
                            continue
                        }

                        got_nickname = true
                        nickname = n

                    case "USER":
                        // Missing parameters.
                        if len(args) < 4 {
                            this.SendNumeric(ERR_NEEDMOREPARAMS, ircd.Me(), "*", cmd)
                            continue
                        }

                        u := args[0]
                        r := args[3]

                        // Check if the username is valid.
                        if ! IsValidUsername(u) {
                            // FIXME: Handle ...
                        }

                        got_username = true

                        // FIXME: We Check Identd later.
                        username = "~" + u
                        realname = r
                }

                if got_nickname && got_username {
                    // Update the client information.  NOTE: It's important to
                    // do this *before* any attempts to register using
                    // this.Register() since otherwise it'll try to register
                    // using the "default" nickname, which is "*", and weird
                    // things may or may not happen.
                    this.nickname = nickname
                    this.username = username
                    this.realname = realname

                    // Try doing a registration.
                    // Check if the nickname has already been registered, just
                    // in case someone else connected whilst we were connecting
                    // and stole our nickname.
                    if ! this.Register() {
                        this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", nickname)
                        continue
                    }

                    // Wait for the synchronisation signal.  We have to do rDNS
                    // lookup and identd check before we proceed to the
                    // registered protocol handler.
                    this.Printf("Waiting for synchronisation")
                    <-sync
                    this.Printf("Synchronisation done")

                    // Let the full IRC protocol handler take over now.
                    go this.RegisteredProtocolHandler()
                    return
                }

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) RegisteredProtocolHandler() {
    this.Printf("Entering Registered Client Protocol Handler!")
    defer this.Printf("Leaving Registered Client Protocol Handler!")

    // The IRCd.
    ircd := this.ircd

    // The server information string.
    server := fmt.Sprintf("%s[%s]", ircd.Me(), this.listener.Address())

    // Welcome the client.
    this.SendNumeric(RPL_WELCOME, ircd.Me(), this.Nickname(), ircd.Description(), this.Nickname())
    this.SendNumeric(RPL_YOURHOST, ircd.Me(), this.Nickname(), server, VersionFull)

    // Send MOTD, if any.
    this.SendMotd()

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Handle the message.
                HandleMessage(this, message)

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) WriteString(s string) {
    this.output<-s
}

func (this *Client) WriteStringF(format string, a...interface{}) {
    this.WriteString(fmt.Sprintf(format, a...))
}

func (this *Client) SendNumeric(n Numeric, a...interface{}) {
    this.WriteString(Format(n, a...))
}

func (this *Client) Unregister() {
    this.Printf("Unregistering client")

    if this.isRegistered {
        this.ircd.UnregisterClient(this)
    }
}

func (this *Client) Register() bool {
    this.Printf("Trying to register client")

    if this.isRegistered {
        panic("Bug: Trying to register an already registered client.")
    }

    b := this.ircd.RegisterClient(this)

    this.isRegistered = b
    return b
}

func (this *Client) SendMotd() {
    ircd := this.ircd
    motd := ircd.Motd()

    if motd == nil {
        this.SendNumeric(ERR_NOMOTD, ircd.Me(), this.Nickname())
        return
    }

    lines := *motd

    this.SendNumeric(RPL_MOTDSTART, ircd.Me(), this.Nickname(), ircd.Me())

    for i := range lines {
        this.SendNumeric(RPL_MOTD, ircd.Me(), this.Nickname(), lines[i])
    }

    this.SendNumeric(RPL_ENDOFMOTD, ircd.Me(), this.Nickname())
}

func (this *Client) Quit(message string) {
    this.WriteStringF(":%s QUIT :%s", this, message)

    // The Socket Input Handler will start the chain that kills the other
    // processes.
    this.Close()
}

func (this *Client) Pong(message string) {
    this.WriteStringF("PONG :%s", message)
}

func (this *Client) Close() {
    this.Printf("Closing")
    defer this.Printf("Finished Closing")

    // Unregister the client.
    this.Unregister()

    // Cleanup.
    this.connection.Close()
}

func (this *Client) PrivateMessage(message *PrivateMessage) {
    this.WriteString(message.String())
}

func (this *Client) Printf(format string, a...interface{}) {
    this.ircd.Printf(this.String() + ": " + format, a...)
}

func (this *Client) RemotePort() int {
    return this.port
}

func (this *Client) RemoteAddress() string {
    return this.address
}

func (this *Client) Nickname() string {
    return this.nickname
}

func (this *Client) Username() string {
    return this.username
}

func (this *Client) Hostname() string {
    return this.hostname
}

func (this *Client) Realname() string {
    return this.realname
}

func (this *Client) Join(channel *Channel) {
    channel.Join(this)
    this.channels.Insert(channel)
}

func (this *Client) Part(channel *Channel) {
    channel.Part(this)
    this.channels.Delete(channel)
}

func (this *Client) ChannelJoin(source *Client, channel *Channel) {
    this.WriteStringF(":%s JOIN :%s", source, channel)
}

func (this *Client) ChannelPart(source *Client, channel *Channel) {
    this.WriteStringF(":%s PART :%s", source, channel)
}

// FIXME: Should die in a refactoring session. Only needed by the
// CommandRegistry.
func (this *Client) Ircd() *Ircd {
    return this.ircd
}

func (this *Client) String() string {
    return fmt.Sprintf("%s!%s@%s", this.Nickname(), this.Username(), this.Hostname())
}
<commit_msg>Hack: PART channels before sending the final QUIT.<commit_after>/* vim: set sw=4 sts=4 et foldmethod=syntax : */

/*
 * Copyright (c) 2011 Alexander Færøy <ahf@0x90.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
    "bufio"
    "fmt"
    "net"
    "strconv"
    "strings"
    "time"
)

type Client struct {
    input chan *Message // Parsed IRC messages.
    output chan string // Output.

    output_quit chan bool // Leave the Output Handler.
    protocol_handler_quit chan bool // Leave the (Un)registered protocol handler.

    nickname string // The Nickname of the client.
    username string // The Username of the client.
    realname string // The Realname/Gecos of the client.
    hostname string // The hostname of the client (after we have done a reverse DNS lookup).

    address string // The IP address of the client.
    port int // The port.

    connection net.Conn // The connection.
    readwriter *bufio.ReadWriter // The buffered reader/writer.
    listener Listener // The listener.

    ircd *Ircd // Pointer to our IRCd.

    channels *ChannelSet // Channels.

    timestamp int64 // Creation time in seconds since UNIX epoch.

    // Housekeeping (FIXME: Needs refactoring).
    isRegistered bool
}

func NewClient(listener Listener, ircd *Ircd, connection net.Conn, remoteAddr string) {
    client := new(Client)

    // The IRCd.
    client.ircd = ircd

    // Timestamp.
    client.timestamp = time.Seconds()

    // IRC Channels.
    client.channels = NewChannelSet()

    // Channels.
    client.input = make(chan *Message)
    client.output = make(chan string)

    // Quit Channels.  These are being used to signal to each of our client
    // processes that it's time to exit.
    client.output_quit = make(chan bool)
    client.protocol_handler_quit = make(chan bool)

    // RemoteAddr is on the form "IP:Port". Split it.
    address, portStr, error := net.SplitHostPort(remoteAddr)

    if error != nil {
        panic("Bug: Unable to split host/port.")
    }

    // Convert it to an integer.
    port, error := strconv.Atoi(portStr)

    if error != nil {
        panic("Bug: Unable to convert port to an int.")
    }

    // Set address and port.
    client.address = address
    client.port = port

    // Start background job for looking up the hostname.
    hostnameCh := LookupAddress(address)

    // Set initial values.
    client.nickname = "*"
    client.username = "*"
    client.realname = "*"

    // Use the IP for now.
    client.hostname = address

    // Set connection, listener and create readwriter.
    client.connection = connection
    client.listener = listener
    client.readwriter = bufio.NewReadWriter(bufio.NewReader(connection), bufio.NewWriter(connection))

    // Used for synchronisation once we are done looking up rDNS and do identd
    // check.
    sync := make(chan bool)

    // Socket Output Handler.
    go client.OutputHandler()

    // Socket Input Handler.
    go client.InputHandler()

    // Unregistered Protocol Handler.  The Unregistered Protocol Handler only
    // accepts either the NICK or USER command. Once the client has been
    // registered, the UnregisteredProtocolHandler() will die and the
    // RegisteredProtocolHandler() will take over.
    go client.UnregisteredProtocolHandler(sync)

    // Start sending IRC messages to the client.
    client.WriteStringF("NOTICE AUTH :*** Processing connection to %s ...", ircd.Me())
    client.WriteString("NOTICE AUTH :*** Looking up your hostname ...")

    // Get result from rDNS lookup.
    host := <-hostnameCh
    client.WriteStringF("NOTICE AUTH :*** Found your hostname (%s) ...", host)
    client.hostname = host

    // Let the UnregisteredProtocolHandler() know that it's okay to exit and
    // let the RegisteredProtocolHandler() take over now.
    sync<-true
}

func (this *Client) InputHandler() {
    this.Printf("Entering Socket Input Handler")
    defer this.Printf("Leaving Socket Input Handler")

    rw := this.readwriter

    for {
        // Read until a newline.
        s, error := rw.ReadString('\n')

        if error != nil {
            // Read Error (Usually happens when the connection is closed by the
            // client).
            // FIXME: Should probably check for non-EOF errors.
            this.Printf("Read Error: %s", error)

            // Start a chain of Quit-messages to the various client processes
            // to ensure that they are all shutdown gracefully. The goal of
            // this is to avoid any dangling processes.
            this.output_quit<-true

            // Exit.
            return
        }

        // Trim "\r\n".
        line := strings.TrimRight(s, "\r\n")

        // Skip "empty" lines.
        if len(Strip(line)) == 0 {
            continue
        }

        // Parse.
        message := Parse(line)

        // Most ASCII strings are valid IRC commands, but in the
        // unlikely case that Parse() returns nil, we'll simply skip
        // it.
        if message != nil {
            this.Printf("-> '%s'", message)

            // Pass on to the ProtocolHandler (Either Registered or Unregistered).
            this.input<-message
        }
    }
}

func (this *Client) OutputHandler() {
    this.Printf("Entering Socket Output Handler")
    defer this.Printf("Leaving Socket Output Handler")

    defer func () {
        this.Printf("Sending Quit-message to the Protocol Handler")
        this.protocol_handler_quit<-true
    }()

    rw := this.readwriter

    for {
        select {
            case <-this.output_quit:
                // We were told to exit. Notice the above defer statements
                // where we chain the quit message on to the message handler
                // process.
                return

            case s, ok := <-this.output:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Ignore the byte-count for now.  Go sets error to non-nil if
                // bytes != len(input).
                _, error := rw.WriteString(s + "\r\n")

                if error != nil {
                    this.Printf("Write Error: %s", error)

                    // Unlikely, but we'll quit.
                    return
                }

                this.Printf("<- '%s'", s)

                // Flush the buffer.
                rw.Flush()
        }
    }
}

func (this *Client) UnregisteredProtocolHandler(sync chan bool) {
    this.Printf("Entering Unregistered Client Protocol Handler")
    defer this.Printf("Leaving Unregistered Client Protocol Handler")

    // The IRCd.
    ircd := this.ircd

    // Housekeeping.
    got_nickname := false
    got_username := false

    // Temporaries.
    var nickname string
    var username string
    var realname string

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // This client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                cmd := message.Command()
                args := message.Arguments()

                switch cmd {
                    case "NICK":
                        // Missing nickname.
                        if len(args) < 1 {
                            this.SendNumeric(ERR_NONICKNAMEGIVEN, ircd.Me(), "*")
                            continue
                        }

                        n := args[0]

                        // Check if the nickname is valid.
                        if ! IsValidNickname(n) {
                            this.SendNumeric(ERR_ERRONEUSNICKNAME, ircd.Me(), "*", n)
                            continue
                        }

                        // Check if the nickname is already registered.
                        if this.ircd.FindClient(n) != nil {
                            this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", n)
                            continue
                        }

                        got_nickname = true
                        nickname = n

                    case "USER":
                        // Missing parameters.
                        if len(args) < 4 {
                            this.SendNumeric(ERR_NEEDMOREPARAMS, ircd.Me(), "*", cmd)
                            continue
                        }

                        u := args[0]
                        r := args[3]

                        // Check if the username is valid.
                        if ! IsValidUsername(u) {
                            // FIXME: Handle ...
                        }

                        got_username = true

                        // FIXME: We Check Identd later.
                        username = "~" + u
                        realname = r
                }

                if got_nickname && got_username {
                    // Update the client information.  NOTE: It's important to
                    // do this *before* any attempts to register using
                    // this.Register() since otherwise it'll try to register
                    // using the "default" nickname, which is "*", and weird
                    // things may or may not happen.
                    this.nickname = nickname
                    this.username = username
                    this.realname = realname

                    // Try doing a registration.
                    // Check if the nickname has already been registered, just
                    // in case someone else connected whilst we were connecting
                    // and stole our nickname.
                    if ! this.Register() {
                        this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", nickname)
                        continue
                    }

                    // Wait for the synchronisation signal.  We have to do rDNS
                    // lookup and identd check before we proceed to the
                    // registered protocol handler.
                    this.Printf("Waiting for synchronisation")
                    <-sync
                    this.Printf("Synchronisation done")

                    // Let the full IRC protocol handler take over now.
                    go this.RegisteredProtocolHandler()
                    return
                }

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) RegisteredProtocolHandler() {
    this.Printf("Entering Registered Client Protocol Handler!")
    defer this.Printf("Leaving Registered Client Protocol Handler!")

    // The IRCd.
    ircd := this.ircd

    // The server information string.
    server := fmt.Sprintf("%s[%s]", ircd.Me(), this.listener.Address())

    // Welcome the client.
    this.SendNumeric(RPL_WELCOME, ircd.Me(), this.Nickname(), ircd.Description(), this.Nickname())
    this.SendNumeric(RPL_YOURHOST, ircd.Me(), this.Nickname(), server, VersionFull)

    // Send MOTD, if any.
    this.SendMotd()

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Handle the message.
                HandleMessage(this, message)

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) WriteString(s string) {
    this.output<-s
}

func (this *Client) WriteStringF(format string, a...interface{}) {
    this.WriteString(fmt.Sprintf(format, a...))
}

func (this *Client) SendNumeric(n Numeric, a...interface{}) {
    this.WriteString(Format(n, a...))
}

func (this *Client) Unregister() {
    this.Printf("Unregistering client")

    if this.isRegistered {
        this.ircd.UnregisterClient(this)
    }
}

func (this *Client) Register() bool {
    this.Printf("Trying to register client")

    if this.isRegistered {
        panic("Bug: Trying to register an already registered client.")
    }

    b := this.ircd.RegisterClient(this)

    this.isRegistered = b
    return b
}

func (this *Client) SendMotd() {
    ircd := this.ircd
    motd := ircd.Motd()

    if motd == nil {
        this.SendNumeric(ERR_NOMOTD, ircd.Me(), this.Nickname())
        return
    }

    lines := *motd

    this.SendNumeric(RPL_MOTDSTART, ircd.Me(), this.Nickname(), ircd.Me())

    for i := range lines {
        this.SendNumeric(RPL_MOTD, ircd.Me(), this.Nickname(), lines[i])
    }

    this.SendNumeric(RPL_ENDOFMOTD, ircd.Me(), this.Nickname())
}

func (this *Client) Quit(message string) {
    // FIXME: Hack: Should be fixed like the description in bug #13.
    // Send a PART for now.
    this.channels.ForEach(func (channel *Channel) {
        channel.Part(this)
    })

    this.WriteStringF(":%s QUIT :%s", this, message)

    // The Socket Input Handler will start the chain that kills the other
    // processes.
    this.Close()
}

func (this *Client) Pong(message string) {
    this.WriteStringF("PONG :%s", message)
}

func (this *Client) Close() {
    this.Printf("Closing")
    defer this.Printf("Finished Closing")

    // Unregister the client.
    this.Unregister()

    // Cleanup.
    this.connection.Close()
}

func (this *Client) PrivateMessage(message *PrivateMessage) {
    this.WriteString(message.String())
}

func (this *Client) Printf(format string, a...interface{}) {
    this.ircd.Printf(this.String() + ": " + format, a...)
}

func (this *Client) RemotePort() int {
    return this.port
}

func (this *Client) RemoteAddress() string {
    return this.address
}

func (this *Client) Nickname() string {
    return this.nickname
}

func (this *Client) Username() string {
    return this.username
}

func (this *Client) Hostname() string {
    return this.hostname
}

func (this *Client) Realname() string {
    return this.realname
}

func (this *Client) Join(channel *Channel) {
    channel.Join(this)
    this.channels.Insert(channel)
}

func (this *Client) Part(channel *Channel) {
    channel.Part(this)
    this.channels.Delete(channel)
}

func (this *Client) ChannelJoin(source *Client, channel *Channel) {
    this.WriteStringF(":%s JOIN :%s", source, channel)
}

func (this *Client) ChannelPart(source *Client, channel *Channel) {
    this.WriteStringF(":%s PART :%s", source, channel)
}

// FIXME: Should die in a refactoring session. Only needed by the
// CommandRegistry.
func (this *Client) Ircd() *Ircd {
    return this.ircd
}

func (this *Client) String() string {
    return fmt.Sprintf("%s!%s@%s", this.Nickname(), this.Username(), this.Hostname())
}
<|endoftext|>


<commit_before>package main

import (
	"net/http/httptest"
	"time"

	"github.com/flynn/flynn/discoverd/testutil"
	"github.com/flynn/flynn/router/client"
	"github.com/flynn/flynn/router/types"
	. "github.com/flynn/go-check"
)

func (s *S) newTestAPIServer(t testutil.TestingT) *testAPIServer {
	httpListener := s.newHTTPListener(t)
	tcpListener := s.newTCPListener(t)
	r := &Router{
		HTTP: httpListener,
		TCP:  tcpListener,
	}
	ts := &testAPIServer{
		Server:    httptest.NewServer(apiHandler(r)),
		listeners: []Listener{r.HTTP, r.TCP},
	}

	ts.Client = client.NewWithAddr(ts.Listener.Addr().String())
	return ts
}

type testAPIServer struct {
	client.Client
	*httptest.Server
	listeners []Listener
}

func (s *testAPIServer) Close() error {
	s.Server.Close()
	for _, l := range s.listeners {
		l.Close()
	}
	return nil
}

func (s *S) TestAPIAddTCPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.TCPRoute{Service: "test"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)

	tcpRoute := r.TCPRoute()
	c.Assert(tcpRoute.ID, Not(Equals), "")
	c.Assert(tcpRoute.CreatedAt, Not(IsNil))
	c.Assert(tcpRoute.UpdatedAt, Not(IsNil))
	c.Assert(tcpRoute.Service, Equals, "test")
	c.Assert(tcpRoute.Port, Not(Equals), 0)
	c.Assert(tcpRoute.Leader, Equals, false)

	route, err := srv.GetRoute("tcp", tcpRoute.ID)
	c.Assert(err, IsNil)

	getTCPRoute := route.TCPRoute()
	c.Assert(getTCPRoute.ID, Equals, tcpRoute.ID)
	c.Assert(getTCPRoute.CreatedAt, DeepEquals, tcpRoute.CreatedAt)
	c.Assert(getTCPRoute.UpdatedAt, DeepEquals, tcpRoute.UpdatedAt)
	c.Assert(getTCPRoute.Service, Equals, "test")
	c.Assert(getTCPRoute.Port, Equals, tcpRoute.Port)
	c.Assert(getTCPRoute.Leader, Equals, false)

	err = srv.DeleteRoute("tcp", route.ID)
	c.Assert(err, IsNil)
	_, err = srv.GetRoute("tcp", route.ID)
	c.Assert(err, Equals, client.ErrNotFound)
}

func (s *S) TestAPIAddHTTPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.HTTPRoute{Domain: "example.com", Service: "test"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)

	httpRoute := r.HTTPRoute()
	c.Assert(httpRoute.ID, Not(Equals), "")
	c.Assert(httpRoute.CreatedAt, Not(IsNil))
	c.Assert(httpRoute.UpdatedAt, Not(IsNil))
	c.Assert(httpRoute.Service, Equals, "test")
	c.Assert(httpRoute.Domain, Equals, "example.com")
	c.Assert(httpRoute.Sticky, Equals, false)
	c.Assert(httpRoute.Leader, Equals, false)

	route, err := srv.GetRoute("http", httpRoute.ID)
	c.Assert(err, IsNil)
	c.Assert(httpRoute.ID, Not(Equals), "")

	getHTTPRoute := route.HTTPRoute()
	c.Assert(getHTTPRoute.ID, Equals, httpRoute.ID)
	c.Assert(getHTTPRoute.CreatedAt, DeepEquals, httpRoute.CreatedAt)
	c.Assert(getHTTPRoute.UpdatedAt, DeepEquals, httpRoute.UpdatedAt)
	c.Assert(getHTTPRoute.Service, Equals, "test")
	c.Assert(getHTTPRoute.Domain, Equals, "example.com")
	c.Assert(getHTTPRoute.Sticky, Equals, false)
	c.Assert(getHTTPRoute.Leader, Equals, false)

	err = srv.DeleteRoute("http", route.ID)
	c.Assert(err, IsNil)
	_, err = srv.GetRoute("http", route.ID)
	c.Assert(err, Equals, client.ErrNotFound)
}

func (s *S) TestAPIAddDuplicateRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	// first create route
	r := router.HTTPRoute{Domain: "example.com", Service: "test"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)

	// ensure we got back what we expect
	route := r.HTTPRoute()
	c.Assert(route.ID, Not(Equals), "")
	c.Assert(route.CreatedAt, Not(IsNil))
	c.Assert(route.UpdatedAt, Not(IsNil))
	c.Assert(route.Service, Equals, "test")
	c.Assert(route.Domain, Equals, "example.com")

	// attempt to create the same route again, ensure fails with conflict
	err = srv.CreateRoute(r)
	c.Assert(err, Not(IsNil))
	c.Assert(err.Error(), Equals, "conflict: Duplicate route")

	// delete the route
	err = srv.DeleteRoute("http", route.ID)
	c.Assert(err, IsNil)
	_, err = srv.GetRoute("http", route.ID)
	c.Assert(err, Equals, client.ErrNotFound)
}

func (s *S) TestAPISetHTTPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.HTTPRoute{Domain: "example.com", Service: "foo"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)
	c.Assert(r.ID, Not(IsNil))

	r = router.HTTPRoute{ID: r.ID, Domain: "example.com", Service: "bar", Leader: true, Sticky: true}.ToRoute()
	err = srv.UpdateRoute(r)
	c.Assert(err, IsNil)
	r, err = srv.GetRoute("http", r.ID)
	c.Assert(err, IsNil)
	c.Assert(r.Sticky, Equals, true)
	c.Assert(r.Leader, Equals, true)
	c.Assert(r.Service, Equals, "bar")
}

func (s *S) TestAPISetTCPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.TCPRoute{Service: "foo"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)
	c.Assert(r.ID, Not(IsNil))

	r = router.TCPRoute{ID: r.ID, Service: "bar", Port: int(r.Port), Leader: true}.ToRoute()
	err = srv.UpdateRoute(r)
	c.Assert(err, IsNil)
	r, err = srv.GetRoute("tcp", r.ID)
	c.Assert(err, IsNil)
	c.Assert(r.Leader, Equals, true)
	c.Assert(r.Service, Equals, "bar")
}

func (s *S) TestAPIListRoutes(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r0 := router.HTTPRoute{Domain: "example.com", Service: "test"}.ToRoute()
	r1 := router.HTTPRoute{Domain: "example.net", Service: "test", ParentRef: "foo"}.ToRoute()
	r2 := router.TCPRoute{Service: "test"}.ToRoute()
	r3 := router.TCPRoute{Service: "test", ParentRef: "foo"}.ToRoute()

	err := srv.CreateRoute(r0)
	c.Assert(err, IsNil)
	err = srv.CreateRoute(r1)
	c.Assert(err, IsNil)
	err = srv.CreateRoute(r2)
	c.Assert(err, IsNil)
	err = srv.CreateRoute(r3)
	c.Assert(err, IsNil)

	routes, err := srv.ListRoutes("")
	c.Assert(err, IsNil)
	c.Assert(routes, HasLen, 4)
	c.Assert(routes[3].ID, Equals, r0.ID)
	c.Assert(routes[2].ID, Equals, r1.ID)
	c.Assert(routes[1].ID, Equals, r2.ID)
	c.Assert(routes[0].ID, Equals, r3.ID)

	routes, err = srv.ListRoutes("foo")
	c.Assert(err, IsNil)
	c.Assert(routes, HasLen, 2)
	c.Assert(routes[1].ID, Equals, r1.ID)
	c.Assert(routes[0].ID, Equals, r3.ID)
}

func (s *S) TestStreamEvents(c *C) {
	srv := s.newTestAPIServer(c)
	client := srv.Client
	defer srv.Close()

	l := srv.listeners[0].(*HTTPListener)
	tcpl := srv.listeners[1].(*TCPListener)

	events := make(chan *router.StreamEvent)
	stream, err := client.StreamEvents(events)
	c.Assert(err, IsNil)
	defer stream.Close()

	r := addHTTPRoute(c, l)
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "set")
		c.Assert(e.Route.ID, Equals, r.ID)
		c.Assert(e.Route.Type, Equals, "http")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for set event")
	}

	removeRoute(c, l, r.ID)
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "remove")
		c.Assert(e.Route.ID, Equals, r.ID)
		c.Assert(e.Route.Type, Equals, "http")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for remove event")
	}

	tcpr := addTCPRoute(c, tcpl, allocatePort())
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "set")
		c.Assert(e.Route.ID, Equals, tcpr.ID)
		c.Assert(e.Route.Type, Equals, "tcp")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for set event")
	}

	removeRoute(c, tcpl, tcpr.ID)
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "remove")
		c.Assert(e.Route.ID, Equals, tcpr.ID)
		c.Assert(e.Route.Type, Equals, "tcp")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for remove event")
	}
}
<commit_msg>router: Test that certificates are included when listing routes<commit_after>package main

import (
	"net/http/httptest"
	"strings"
	"time"

	"github.com/flynn/flynn/discoverd/testutil"
	"github.com/flynn/flynn/router/client"
	"github.com/flynn/flynn/router/types"
	. "github.com/flynn/go-check"
)

func (s *S) newTestAPIServer(t testutil.TestingT) *testAPIServer {
	httpListener := s.newHTTPListener(t)
	tcpListener := s.newTCPListener(t)
	r := &Router{
		HTTP: httpListener,
		TCP:  tcpListener,
	}
	ts := &testAPIServer{
		Server:    httptest.NewServer(apiHandler(r)),
		listeners: []Listener{r.HTTP, r.TCP},
	}

	ts.Client = client.NewWithAddr(ts.Listener.Addr().String())
	return ts
}

type testAPIServer struct {
	client.Client
	*httptest.Server
	listeners []Listener
}

func (s *testAPIServer) Close() error {
	s.Server.Close()
	for _, l := range s.listeners {
		l.Close()
	}
	return nil
}

func (s *S) TestAPIAddTCPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.TCPRoute{Service: "test"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)

	tcpRoute := r.TCPRoute()
	c.Assert(tcpRoute.ID, Not(Equals), "")
	c.Assert(tcpRoute.CreatedAt, Not(IsNil))
	c.Assert(tcpRoute.UpdatedAt, Not(IsNil))
	c.Assert(tcpRoute.Service, Equals, "test")
	c.Assert(tcpRoute.Port, Not(Equals), 0)
	c.Assert(tcpRoute.Leader, Equals, false)

	route, err := srv.GetRoute("tcp", tcpRoute.ID)
	c.Assert(err, IsNil)

	getTCPRoute := route.TCPRoute()
	c.Assert(getTCPRoute.ID, Equals, tcpRoute.ID)
	c.Assert(getTCPRoute.CreatedAt, DeepEquals, tcpRoute.CreatedAt)
	c.Assert(getTCPRoute.UpdatedAt, DeepEquals, tcpRoute.UpdatedAt)
	c.Assert(getTCPRoute.Service, Equals, "test")
	c.Assert(getTCPRoute.Port, Equals, tcpRoute.Port)
	c.Assert(getTCPRoute.Leader, Equals, false)

	err = srv.DeleteRoute("tcp", route.ID)
	c.Assert(err, IsNil)
	_, err = srv.GetRoute("tcp", route.ID)
	c.Assert(err, Equals, client.ErrNotFound)
}

func (s *S) TestAPIAddHTTPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.HTTPRoute{Domain: "example.com", Service: "test"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)

	httpRoute := r.HTTPRoute()
	c.Assert(httpRoute.ID, Not(Equals), "")
	c.Assert(httpRoute.CreatedAt, Not(IsNil))
	c.Assert(httpRoute.UpdatedAt, Not(IsNil))
	c.Assert(httpRoute.Service, Equals, "test")
	c.Assert(httpRoute.Domain, Equals, "example.com")
	c.Assert(httpRoute.Sticky, Equals, false)
	c.Assert(httpRoute.Leader, Equals, false)

	route, err := srv.GetRoute("http", httpRoute.ID)
	c.Assert(err, IsNil)
	c.Assert(httpRoute.ID, Not(Equals), "")

	getHTTPRoute := route.HTTPRoute()
	c.Assert(getHTTPRoute.ID, Equals, httpRoute.ID)
	c.Assert(getHTTPRoute.CreatedAt, DeepEquals, httpRoute.CreatedAt)
	c.Assert(getHTTPRoute.UpdatedAt, DeepEquals, httpRoute.UpdatedAt)
	c.Assert(getHTTPRoute.Service, Equals, "test")
	c.Assert(getHTTPRoute.Domain, Equals, "example.com")
	c.Assert(getHTTPRoute.Sticky, Equals, false)
	c.Assert(getHTTPRoute.Leader, Equals, false)

	err = srv.DeleteRoute("http", route.ID)
	c.Assert(err, IsNil)
	_, err = srv.GetRoute("http", route.ID)
	c.Assert(err, Equals, client.ErrNotFound)
}

func (s *S) TestAPIAddDuplicateRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	// first create route
	r := router.HTTPRoute{Domain: "example.com", Service: "test"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)

	// ensure we got back what we expect
	route := r.HTTPRoute()
	c.Assert(route.ID, Not(Equals), "")
	c.Assert(route.CreatedAt, Not(IsNil))
	c.Assert(route.UpdatedAt, Not(IsNil))
	c.Assert(route.Service, Equals, "test")
	c.Assert(route.Domain, Equals, "example.com")

	// attempt to create the same route again, ensure fails with conflict
	err = srv.CreateRoute(r)
	c.Assert(err, Not(IsNil))
	c.Assert(err.Error(), Equals, "conflict: Duplicate route")

	// delete the route
	err = srv.DeleteRoute("http", route.ID)
	c.Assert(err, IsNil)
	_, err = srv.GetRoute("http", route.ID)
	c.Assert(err, Equals, client.ErrNotFound)
}

func (s *S) TestAPISetHTTPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.HTTPRoute{Domain: "example.com", Service: "foo"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)
	c.Assert(r.ID, Not(IsNil))

	r = router.HTTPRoute{ID: r.ID, Domain: "example.com", Service: "bar", Leader: true, Sticky: true}.ToRoute()
	err = srv.UpdateRoute(r)
	c.Assert(err, IsNil)
	r, err = srv.GetRoute("http", r.ID)
	c.Assert(err, IsNil)
	c.Assert(r.Sticky, Equals, true)
	c.Assert(r.Leader, Equals, true)
	c.Assert(r.Service, Equals, "bar")
}

func (s *S) TestAPISetTCPRoute(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r := router.TCPRoute{Service: "foo"}.ToRoute()
	err := srv.CreateRoute(r)
	c.Assert(err, IsNil)
	c.Assert(r.ID, Not(IsNil))

	r = router.TCPRoute{ID: r.ID, Service: "bar", Port: int(r.Port), Leader: true}.ToRoute()
	err = srv.UpdateRoute(r)
	c.Assert(err, IsNil)
	r, err = srv.GetRoute("tcp", r.ID)
	c.Assert(err, IsNil)
	c.Assert(r.Leader, Equals, true)
	c.Assert(r.Service, Equals, "bar")
}

func (s *S) TestAPIListRoutes(c *C) {
	srv := s.newTestAPIServer(c)
	defer srv.Close()

	r0 := router.HTTPRoute{Domain: "example.com", Service: "test"}.ToRoute()
	r1 := router.HTTPRoute{Domain: "example.net", Service: "test", ParentRef: "foo"}.ToRoute()
	r2 := router.TCPRoute{Service: "test"}.ToRoute()
	r3 := router.TCPRoute{Service: "test", ParentRef: "foo"}.ToRoute()

	tlsCert := tlsConfigForDomain("*.bar.example.org")
	r4 := router.HTTPRoute{
		Domain:  "1.bar.example.org",
		Service: "test",
		Certificate: &router.Certificate{
			Cert: tlsCert.Cert,
			Key:  tlsCert.PrivateKey,
		},
	}.ToRoute()

	err := srv.CreateRoute(r0)
	c.Assert(err, IsNil)
	err = srv.CreateRoute(r1)
	c.Assert(err, IsNil)
	err = srv.CreateRoute(r2)
	c.Assert(err, IsNil)
	err = srv.CreateRoute(r3)
	c.Assert(err, IsNil)
	err = srv.CreateRoute(r4)
	c.Assert(err, IsNil)

	routes, err := srv.ListRoutes("")
	c.Assert(err, IsNil)
	c.Assert(routes, HasLen, 5)
	c.Assert(routes[4].ID, Equals, r0.ID)
	c.Assert(routes[3].ID, Equals, r1.ID)
	c.Assert(routes[2].ID, Equals, r2.ID)
	c.Assert(routes[1].ID, Equals, r3.ID)
	c.Assert(routes[0].ID, Equals, r4.ID)

	c.Assert(routes[0].Certificate, Not(IsNil))
	c.Assert(routes[0].Certificate.Cert, Equals, strings.TrimSuffix(tlsCert.Cert, "\n"))
	c.Assert(routes[0].Certificate.Key, Equals, strings.TrimSuffix(tlsCert.PrivateKey, "\n"))

	routes, err = srv.ListRoutes("foo")
	c.Assert(err, IsNil)
	c.Assert(routes, HasLen, 2)
	c.Assert(routes[1].ID, Equals, r1.ID)
	c.Assert(routes[0].ID, Equals, r3.ID)
}

func (s *S) TestStreamEvents(c *C) {
	srv := s.newTestAPIServer(c)
	client := srv.Client
	defer srv.Close()

	l := srv.listeners[0].(*HTTPListener)
	tcpl := srv.listeners[1].(*TCPListener)

	events := make(chan *router.StreamEvent)
	stream, err := client.StreamEvents(events)
	c.Assert(err, IsNil)
	defer stream.Close()

	r := addHTTPRoute(c, l)
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "set")
		c.Assert(e.Route.ID, Equals, r.ID)
		c.Assert(e.Route.Type, Equals, "http")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for set event")
	}

	removeRoute(c, l, r.ID)
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "remove")
		c.Assert(e.Route.ID, Equals, r.ID)
		c.Assert(e.Route.Type, Equals, "http")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for remove event")
	}

	tcpr := addTCPRoute(c, tcpl, allocatePort())
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "set")
		c.Assert(e.Route.ID, Equals, tcpr.ID)
		c.Assert(e.Route.Type, Equals, "tcp")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for set event")
	}

	removeRoute(c, tcpl, tcpr.ID)
	select {
	case e, ok := <-events:
		if !ok {
			c.Fatal("unexpected close of event stream")
		}
		c.Assert(e.Event, Equals, "remove")
		c.Assert(e.Route.ID, Equals, tcpr.ID)
		c.Assert(e.Route.Type, Equals, "tcp")
		c.Assert(e.Error, IsNil)
	case <-time.After(10 * time.Second):
		c.Fatal("Timed out waiting for remove event")
	}
}
<|endoftext|>


<commit_before>package zmqtransport

import (
	"fmt"

	zmq "github.com/pebbe/zmq4"

	"git.apache.org/thrift.git/lib/go/thrift"
)

type WriteMessage struct {
	id   []byte
	data []byte
}

// TRouterServerTransport is a zeromq Router based TServerTransport implementation.
type TRouterServerTransport struct {
	transports map[string]TRouterTransport
	sock       *zmq.Socket
	endpoint   string
	cwrite     chan WriteMessage
	caccept    chan []byte
}

func NewTRouterServerTransport(endpoint string) TRouterServerTransport {
	fmt.Println("new TRouterServerTransport")
	sock, err := zmq.NewSocket(zmq.ROUTER)

	if err != nil {
		panic(err.Error())
	}

	cwrite := make(chan WriteMessage, 100)
	caccept := make(chan []byte, 100)
	go func() {
		for {
			msg := <-cwrite
			sock.SendMessage(msg.id, msg.data)
			//fmt.Println("send ", msg)
		}
	}()

	t := TRouterServerTransport{
		transports: make(map[string]TRouterTransport),
		sock:       sock,
		endpoint:   endpoint,
		caccept:    caccept,
		cwrite:     cwrite,
	}
	go func() {
		for {
			msg, err := sock.RecvMessageBytes(0)
			//fmt.Println("number of peers ", len(t.transports))
			id := msg[0]
			var transport TRouterTransport
			if err == nil {
				buf := msg[1]

				if trans, ok := t.transports[string(id)]; !ok {
					cread := make(chan []byte, 100)
					transport = TRouterTransport{
						id:     id,
						cread:  cread,
						cwrite: t.cwrite,
						server: &t,
					}
					fmt.Println("create transport channel for", transport)

					t.transports[string(id)] = transport
					caccept <- id
				} else {
					transport = trans
				}
				if len(buf) == 0 {
					// received 0 bytes means close
					fmt.Println("received EOL")
					transport.Close()
				} else {
					transport.cread <- buf
				}
			}
		}
	}()
	return t
}

func (t TRouterServerTransport) Listen() error {
	t.sock.Bind(t.endpoint)
	fmt.Println("TRouterServerTransport.Listen ", t.endpoint)
	return nil
}

func (t TRouterServerTransport) Accept() (thrift.TTransport, error) {
	id := <-t.caccept
	fmt.Println("TRouterServerTransport.Accept ", id, t.transports[string(id)])
	return t.transports[string(id)], nil
}

func (t TRouterServerTransport) Close() error {
	fmt.Println("TRouterServerTransport.Close")

	transports := make([]TRouterTransport, 0)
	for _, trans := range t.transports {
		transports = append(transports, trans)
	}
	for _, trans := range transports {
		trans.Close()
	}
	return nil
}

func (t TRouterServerTransport) Interrupt() error {
	fmt.Println("TRouterServerTransport.Interrupt")
	return nil
}

// TRouterTransport is a TTransport implementation.
type TRouterTransport struct {
	id     []byte
	cread  chan []byte
	cwrite chan WriteMessage
	server *TRouterServerTransport
}

func (t TRouterTransport) Open() error {
	return nil
}

func (t TRouterTransport) IsOpen() bool {
	return true
}

func (t TRouterTransport) Close() error {
	fmt.Println("TRouterTransport.Close")
	t.cwrite <- WriteMessage{t.id, []byte{}} // send 0 bytes indicates close
	delete(t.server.transports, string(t.id))
	return nil
}

func (t TRouterTransport) Read(buf []byte) (l int, err error) {
	//fmt.Println("TRouterTransport.Read wait, len ", len(buf), buf)
	data := <-t.cread
	copy(buf, data)
	return len(data), nil
}

func (t TRouterTransport) Write(buf []byte) (int, error) {
	data := make([]byte, len(buf))
	copy(data, buf)
	msg := WriteMessage{
		id:   t.id,
		data: data,
	}

	t.cwrite <- msg
	//fmt.Println("write ", msg)
	return len(buf), nil
}

func (p TRouterTransport) Flush() error {
	return nil
}

func (p TRouterTransport) RemainingBytes() (num_bytes uint64) {
	//fmt.Println("RemainingBytes, hard code to 128")
	return uint64(4096)
}
<commit_msg>add comment<commit_after>package zmqtransport

import (
	"fmt"

	zmq "github.com/pebbe/zmq4"

	"git.apache.org/thrift.git/lib/go/thrift"
)

type WriteMessage struct {
	id   []byte
	data []byte
}

// TRouterServerTransport is a zeromq Router based TServerTransport implementation.
type TRouterServerTransport struct {
	transports map[string]TRouterTransport
	sock       *zmq.Socket
	endpoint   string
	cwrite     chan WriteMessage
	caccept    chan []byte
}

// NewTRouterServerTransport function instantiates a new TRouterServerTransport to specified endpoint
func NewTRouterServerTransport(endpoint string) TRouterServerTransport {
	fmt.Println("new TRouterServerTransport")
	sock, err := zmq.NewSocket(zmq.ROUTER)

	if err != nil {
		panic(err.Error())
	}

	cwrite := make(chan WriteMessage, 100)
	caccept := make(chan []byte, 100)
	go func() {
		for {
			msg := <-cwrite
			sock.SendMessage(msg.id, msg.data)
			//fmt.Println("send ", msg)
		}
	}()

	t := TRouterServerTransport{
		transports: make(map[string]TRouterTransport),
		sock:       sock,
		endpoint:   endpoint,
		caccept:    caccept,
		cwrite:     cwrite,
	}
	go func() {
		for {
			msg, err := sock.RecvMessageBytes(0)
			//fmt.Println("number of peers ", len(t.transports))
			id := msg[0]
			var transport TRouterTransport
			if err == nil {
				buf := msg[1]

				if trans, ok := t.transports[string(id)]; !ok {
					cread := make(chan []byte, 100)
					transport = TRouterTransport{
						id:     id,
						cread:  cread,
						cwrite: t.cwrite,
						server: &t,
					}
					fmt.Println("create transport channel for", transport)

					t.transports[string(id)] = transport
					caccept <- id
				} else {
					transport = trans
				}
				if len(buf) == 0 {
					// received 0 bytes means close
					fmt.Println("received EOL")
					transport.Close()
				} else {
					transport.cread <- buf
				}
			}
		}
	}()
	return t
}

func (t TRouterServerTransport) Listen() error {
	t.sock.Bind(t.endpoint)
	fmt.Println("TRouterServerTransport.Listen ", t.endpoint)
	return nil
}

func (t TRouterServerTransport) Accept() (thrift.TTransport, error) {
	id := <-t.caccept
	fmt.Println("TRouterServerTransport.Accept ", id, t.transports[string(id)])
	return t.transports[string(id)], nil
}

func (t TRouterServerTransport) Close() error {
	fmt.Println("TRouterServerTransport.Close")

	transports := make([]TRouterTransport, 0)
	for _, trans := range t.transports {
		transports = append(transports, trans)
	}
	for _, trans := range transports {
		trans.Close()
	}
	return nil
}

func (t TRouterServerTransport) Interrupt() error {
	fmt.Println("TRouterServerTransport.Interrupt")
	return nil
}

// TRouterTransport is a TTransport implementation.
type TRouterTransport struct {
	id     []byte
	cread  chan []byte
	cwrite chan WriteMessage
	server *TRouterServerTransport
}

func (t TRouterTransport) Open() error {
	return nil
}

func (t TRouterTransport) IsOpen() bool {
	return true
}

func (t TRouterTransport) Close() error {
	fmt.Println("TRouterTransport.Close")
	t.cwrite <- WriteMessage{t.id, []byte{}} // send 0 bytes indicates close
	delete(t.server.transports, string(t.id))
	return nil
}

func (t TRouterTransport) Read(buf []byte) (l int, err error) {
	//fmt.Println("TRouterTransport.Read wait, len ", len(buf), buf)
	data := <-t.cread
	copy(buf, data)
	return len(data), nil
}

func (t TRouterTransport) Write(buf []byte) (int, error) {
	data := make([]byte, len(buf))
	copy(data, buf)
	msg := WriteMessage{
		id:   t.id,
		data: data,
	}

	t.cwrite <- msg
	//fmt.Println("write ", msg)
	return len(buf), nil
}

func (p TRouterTransport) Flush() error {
	return nil
}

func (p TRouterTransport) RemainingBytes() (num_bytes uint64) {
	//fmt.Println("RemainingBytes, hard code to 128")
	return uint64(4096)
}
<|endoftext|>


<commit_before>package schema

import (
	"bytes"
	"fmt"
	"regexp"
	"sort"
	"strings"

	"github.com/cihangir/stringext"
)

var newLinesRegex = regexp.MustCompile(`(?m:\s*$)`)

// Resolve resolves reference inside the schema.
func (s *Schema) Resolve(r *Schema) *Schema {
	if r == nil {
		r = s
	}
	for n, d := range s.Definitions {
		s.Definitions[n] = d.Resolve(r)
	}
	for n, p := range s.Properties {
		s.Properties[n] = p.Resolve(r)
	}
	for n, p := range s.Functions {
		s.Functions[n] = p.Resolve(r)
	}
	for n, p := range s.PatternProperties {
		s.PatternProperties[n] = p.Resolve(r)
	}
	for n, p := range s.Items {
		s.Items[n] = p.Resolve(r)
	}
	if s.Ref != nil {
		s = s.Ref.Resolve(r)
	}
	if len(s.OneOf) > 0 {
		s = s.OneOf[0].Ref.Resolve(r)
	}
	if len(s.AnyOf) > 0 {
		s = s.AnyOf[0].Ref.Resolve(r)
	}
	for _, l := range s.Links {
		l.Resolve(r)
	}
	return s
}

// Types returns the array of types described by this schema.
func (s *Schema) Types() (types []string) {
	if arr, ok := s.Type.([]interface{}); ok {
		for _, v := range arr {
			types = append(types, v.(string))
		}
	} else if str, ok := s.Type.(string); ok {
		types = append(types, str)
	} else {
		panic(fmt.Sprintf("unknown type %v", s.Type))
	}
	return types
}

// GoType returns the Go type for the given schema as string.
func (s *Schema) GoType() string {
	return s.goType(true, true)
}

// IsCustomType returns true if the schema declares a custom type.
func (s *Schema) IsCustomType() bool {
	return len(s.Properties) > 0
}

func (s *Schema) goType(required bool, force bool) (goType string) {
	// Resolve JSON reference/pointer
	types := s.Types()
	for _, kind := range types {
		switch kind {
		case "boolean":
			goType = "bool"
		case "string":
			switch s.Format {
			case "date-time":
				goType = "time.Time"
			default:
				goType = "string"
			}
			// put this out of the switch statement
		case "number":
			// There is a bias toward networking-related formats in the JSON
			// Schema specification, most likely due to its heritage in web
			// technologies. However, custom formats may also be used, as long
			// as the parties exchanging the JSON documents also exchange
			// information about the custom format types. A JSON Schema
			// validator will ignore any format type that it does not
			// understand.
			switch s.Format {
			case "int64":
				goType = "int64"
			case "float32":
				goType = "float32"
			default:
				goType = "float64"
			}
		case "integer":
			goType = "int"
		case "any":
			goType = "interface{}"
		case "array":
			if len(s.Items) == 1 {
				goType = "[]" + s.Items[0].goType(required, force)
			} else {
				goType = "[]interface{}"
			}
		case "object", "config":
			// Check if patternProperties exists.
			if s.PatternProperties != nil {
				for _, prop := range s.PatternProperties {
					goType = fmt.Sprintf("map[string]%s", prop.GoType())
					break // We don't support more than one pattern for now.
				}
				continue
			}
			buf := bytes.NewBufferString("struct {")
			for _, name := range SortedKeys(s.Properties) {
				prop := s.Properties[name]
				req := stringext.Contains(name, s.Required) || force
				templates.ExecuteTemplate(buf, "field.tmpl", struct {
					Definition *Schema
					Name       string
					Required   bool
					Type       string
				}{
					Definition: prop,
					Name:       name,
					Required:   req,
					Type:       prop.goType(req, force),
				})
			}

			buf.WriteString("}")
			goType = buf.String()
		case "null":
			continue
		default:
			panic("unknown field")
		}
	}
	if goType == "" {
		panic(fmt.Sprintf("type not found : %s", types))
	}
	// Types allow null
	if stringext.Contains("null", types) || !(required || force) {
		return "*" + goType
	}
	return goType
}

// Values returns function return values types.
func (s *Schema) Values(name string, l *Link) []string {
	var values []string
	name = stringext.ToUpperFirst(name)
	switch l.Rel {
	case "destroy", "empty":
		values = append(values, "error")
	case "instances":
		values = append(values, fmt.Sprintf("[]*%s", name), "error")
	default:
		if s.IsCustomType() {
			values = append(values, fmt.Sprintf("*%s", name), "error")
		} else {
			values = append(values, s.GoType(), "error")
		}
	}
	return values
}

// Argumentize returns a string that can be used as an argument into a function
func Argumentize(s *Schema) string {
	switch s.Type {
	case "array":
		if len(s.Items) == 1 {
			return fmt.Sprintf("[]*models.%s", s.Items[0].Title)
		} else {
			return "[]interface{}"
		}
	default:
		return fmt.Sprintf("models.%s", s.Title)
	}
}

// URL returns schema base URL.
func (s *Schema) URL() string {
	for _, l := range s.Links {
		if l.Rel == "self" {
			return l.HRef.String()
		}
	}
	return ""
}

// Parameters returns function parameters names and types.
func (l *Link) Parameters() ([]string, map[string]string) {
	if l.HRef == nil {
		// No HRef property
		panic(fmt.Errorf("no href property declared for %s", l.Title))
	}
	var order []string
	params := make(map[string]string)
	for _, name := range l.HRef.Order {
		def := l.HRef.Schemas[name]
		order = append(order, name)
		params[name] = def.GoType()
	}
	switch l.Rel {
	case "update", "create":
		order = append(order, "o")
		params["o"] = l.GoType()
	case "instances":
		order = append(order, "lr")
		params["lr"] = "*ListRange"
	}
	return order, params
}

// Resolve resolve link schema and href.
func (l *Link) Resolve(r *Schema) {
	if l.Schema != nil {
		l.Schema = l.Schema.Resolve(r)
	}
	l.HRef.Resolve(r)
}

// GoType returns Go type for the given schema as string.
func (l *Link) GoType() string {
	return l.Schema.goType(true, false)
}

func SortedKeys(m map[string]*Schema) (keys []string) {
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return
}

// Args creates arguments string
func Args(h *HRef) string {
	return strings.Join(h.Order, ", ")
}

// Values creates the value string
func Values(n string, s *Schema, l *Link) string {
	v := s.Values(n, l)
	return strings.Join(v, ", ")
}

// Required checks if the given n is a required property
func Required(n string, def *Schema) bool {
	return stringext.Contains(n, def.Required)
}

// Params creates the parameter string for the given link
func Params(l *Link) string {
	var p []string
	order, Params := l.Parameters()
	for _, n := range order {
		p = append(p, fmt.Sprintf("%s %s", stringext.DepunctWithInitialLower(n), Params[n]))
	}
	return strings.Join(p, ", ")
}

func goType(p *Schema) string {
	return p.GoType()
}
<commit_msg>Schema: if the title is not set, use the key<commit_after>package schema

import (
	"bytes"
	"fmt"
	"regexp"
	"sort"
	"strings"

	"github.com/cihangir/stringext"
)

var newLinesRegex = regexp.MustCompile(`(?m:\s*$)`)

// Resolve resolves reference inside the schema.
func (s *Schema) Resolve(r *Schema) *Schema {
	if r == nil {
		r = s
	}
	for n, d := range s.Definitions {
		if d.Title == "" {
			d.Title = n
		}
		s.Definitions[n] = d.Resolve(r)
	}
	for n, p := range s.Properties {
		if p.Title == "" {
			p.Title = n
		}
		s.Properties[n] = p.Resolve(r)
	}
	for n, f := range s.Functions {
		if f.Title == "" {
			f.Title = n
		}
		s.Functions[n] = f.Resolve(r)
	}
	for n, p := range s.PatternProperties {
		s.PatternProperties[n] = p.Resolve(r)
	}
	for n, p := range s.Items {
		s.Items[n] = p.Resolve(r)
	}
	if s.Ref != nil {
		s = s.Ref.Resolve(r)
	}
	if len(s.OneOf) > 0 {
		s = s.OneOf[0].Ref.Resolve(r)
	}
	if len(s.AnyOf) > 0 {
		s = s.AnyOf[0].Ref.Resolve(r)
	}
	for _, l := range s.Links {
		l.Resolve(r)
	}
	return s
}

// Types returns the array of types described by this schema.
func (s *Schema) Types() (types []string) {
	if arr, ok := s.Type.([]interface{}); ok {
		for _, v := range arr {
			types = append(types, v.(string))
		}
	} else if str, ok := s.Type.(string); ok {
		types = append(types, str)
	} else {
		panic(fmt.Sprintf("unknown type %v", s.Type))
	}
	return types
}

// GoType returns the Go type for the given schema as string.
func (s *Schema) GoType() string {
	return s.goType(true, true)
}

// IsCustomType returns true if the schema declares a custom type.
func (s *Schema) IsCustomType() bool {
	return len(s.Properties) > 0
}

func (s *Schema) goType(required bool, force bool) (goType string) {
	// Resolve JSON reference/pointer
	types := s.Types()
	for _, kind := range types {
		switch kind {
		case "boolean":
			goType = "bool"
		case "string":
			switch s.Format {
			case "date-time":
				goType = "time.Time"
			default:
				goType = "string"
			}
			// put this out of the switch statement
		case "number":
			// There is a bias toward networking-related formats in the JSON
			// Schema specification, most likely due to its heritage in web
			// technologies. However, custom formats may also be used, as long
			// as the parties exchanging the JSON documents also exchange
			// information about the custom format types. A JSON Schema
			// validator will ignore any format type that it does not
			// understand.
			switch s.Format {
			case "int64":
				goType = "int64"
			case "float32":
				goType = "float32"
			default:
				goType = "float64"
			}
		case "integer":
			goType = "int"
		case "any":
			goType = "interface{}"
		case "array":
			if len(s.Items) == 1 {
				goType = "[]" + s.Items[0].goType(required, force)
			} else {
				goType = "[]interface{}"
			}
		case "object", "config":
			// Check if patternProperties exists.
			if s.PatternProperties != nil {
				for _, prop := range s.PatternProperties {
					goType = fmt.Sprintf("map[string]%s", prop.GoType())
					break // We don't support more than one pattern for now.
				}
				continue
			}
			buf := bytes.NewBufferString("struct {")
			for _, name := range SortedKeys(s.Properties) {
				prop := s.Properties[name]
				req := stringext.Contains(name, s.Required) || force
				templates.ExecuteTemplate(buf, "field.tmpl", struct {
					Definition *Schema
					Name       string
					Required   bool
					Type       string
				}{
					Definition: prop,
					Name:       name,
					Required:   req,
					Type:       prop.goType(req, force),
				})
			}

			buf.WriteString("}")
			goType = buf.String()
		case "null":
			continue
		default:
			panic("unknown field")
		}
	}
	if goType == "" {
		panic(fmt.Sprintf("type not found : %s", types))
	}
	// Types allow null
	if stringext.Contains("null", types) || !(required || force) {
		return "*" + goType
	}
	return goType
}

// Values returns function return values types.
func (s *Schema) Values(name string, l *Link) []string {
	var values []string
	name = stringext.ToUpperFirst(name)
	switch l.Rel {
	case "destroy", "empty":
		values = append(values, "error")
	case "instances":
		values = append(values, fmt.Sprintf("[]*%s", name), "error")
	default:
		if s.IsCustomType() {
			values = append(values, fmt.Sprintf("*%s", name), "error")
		} else {
			values = append(values, s.GoType(), "error")
		}
	}
	return values
}

// Argumentize returns a string that can be used as an argument into a function
func Argumentize(s *Schema) string {
	switch s.Type {
	case "array":
		if len(s.Items) == 1 {
			return fmt.Sprintf("[]*models.%s", s.Items[0].Title)
		} else {
			return "[]interface{}"
		}
	default:
		return fmt.Sprintf("models.%s", s.Title)
	}
}

// URL returns schema base URL.
func (s *Schema) URL() string {
	for _, l := range s.Links {
		if l.Rel == "self" {
			return l.HRef.String()
		}
	}
	return ""
}

// Parameters returns function parameters names and types.
func (l *Link) Parameters() ([]string, map[string]string) {
	if l.HRef == nil {
		// No HRef property
		panic(fmt.Errorf("no href property declared for %s", l.Title))
	}
	var order []string
	params := make(map[string]string)
	for _, name := range l.HRef.Order {
		def := l.HRef.Schemas[name]
		order = append(order, name)
		params[name] = def.GoType()
	}
	switch l.Rel {
	case "update", "create":
		order = append(order, "o")
		params["o"] = l.GoType()
	case "instances":
		order = append(order, "lr")
		params["lr"] = "*ListRange"
	}
	return order, params
}

// Resolve resolve link schema and href.
func (l *Link) Resolve(r *Schema) {
	if l.Schema != nil {
		l.Schema = l.Schema.Resolve(r)
	}
	l.HRef.Resolve(r)
}

// GoType returns Go type for the given schema as string.
func (l *Link) GoType() string {
	return l.Schema.goType(true, false)
}

func SortedKeys(m map[string]*Schema) (keys []string) {
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return
}

// Args creates arguments string
func Args(h *HRef) string {
	return strings.Join(h.Order, ", ")
}

// Values creates the value string
func Values(n string, s *Schema, l *Link) string {
	v := s.Values(n, l)
	return strings.Join(v, ", ")
}

// Required checks if the given n is a required property
func Required(n string, def *Schema) bool {
	return stringext.Contains(n, def.Required)
}

// Params creates the parameter string for the given link
func Params(l *Link) string {
	var p []string
	order, Params := l.Parameters()
	for _, n := range order {
		p = append(p, fmt.Sprintf("%s %s", stringext.DepunctWithInitialLower(n), Params[n]))
	}
	return strings.Join(p, ", ")
}

func goType(p *Schema) string {
	return p.GoType()
}
<|endoftext|>


<commit_before>// Package routing is a wrapper on naoina/denco router.
// It uses request.Form for params instead of a separate Params
// argument. So, it requires a more memory and it is a bit slower.
// However, the downsides are an acceptable trade off for compatability
// with the standard library.
package routing

import (
	"net/http"

	"github.com/naoina/denco"
)

// Router represents a multiplexer for HTTP request.
type Router struct {
	data *denco.Router
}

// info stores information about HTTP request's handler, its
// pattern and methods.
type info struct {
	handler http.HandlerFunc // HTTP request handler.
	methods []string         // A list of allowed HTTP methods (e.g. "GET" or "POST").
	pattern string           // Pattern is a routing path for handler.
}

// New allocates and returns a new multiplexer.
func New() *Router {
	return &Router{}
}

// HasMethod checks whether specific method request is allowed.
func (t *info) HasMethod(name string) bool {
	// We are iterating through a slice of method strings
	// rather than using a map as there are only a few possible values.
	// So, hash function will require more time than a simple loop.
	for _, v := range t.methods {
		if v == name {
			return true
		}
	}
	return false
}

// ServeHTTP is used to implement http.Handler interface.
// It dispatches the request to the handler whose pattern
// most closely matches the request URL.
func (t *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, it is being overridden.
func (t *Router) Handle(method, pattern string, handler http.Handler) {
}

// HandleFunc registers the handler function for the given pattern.
func (t *Router) HandleFunc(method, pattern string, handler http.HandlerFunc) {
}

// Handler returns the handler to use for the given request, consulting r.Method
// and r.URL.Path. It always returns a non-nil handler. If there is no registered handler
// that applies to the request, Handler returns a “page not found” handler and empty pattern.
// If there is a registered handler but requested method is not allowed,
// "method not allowed" and a pattern are returned.
func (t *Router) Handler(r *http.Request) (handler http.Handler, pattern string) {
	// Make sure we have a handler for this request.
	obj, params, found := t.data.Lookup(r.URL.Path)
	if !found {
		return http.HandlerFunc(NotFound), ""
	}

	// Check whether requested method is allowed.
	data := obj.(info)
	if !data.HasMethod(r.Method) {
		return http.HandlerFunc(MethodNotAllowed), data.pattern
	}

	// Add parameters of request to request.Form and return a handler.
	for _, param := range params {
		r.Form.Add(param.Name, param.Value)
	}
	return data.handler, data.pattern
}

// MethodNotAllowed replies to the request with an HTTP 405 method not allowed
// error. If you want to use your own MethodNotAllowed handler, please override
// this variable.
var MethodNotAllowed = func(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
}

// NotFound replies to the request with an HTTP 404 not found error.
// NotFound is called when unknown HTTP method or a handler not found.
// If you want to use the your own NotFound handler, please overwrite this variable.
var NotFound = func(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}
<commit_msg>Implemented router using denco as a base<commit_after>// Package routing is a wrapper on naoina/denco router.
// It uses request.Form for params instead of a separate Params
// argument. So, it requires a more memory and it is a bit slower.
// However, the downsides are an acceptable trade off for compatability
// with the standard library.
//
// A sample of its usage is below:
//
//	package main
//
//	import (
//		"log"
//		"net/http"
//
//		"github.com/anonx/sunplate/routing"
//	)
//
//	func main() {
//		r := routing.New()
//		err := r.Handle(routing.Routes{
//			r.Get("/profiles/:username", ShowUserHandleFunc),
//			r.Delete("/profiles/:username", DeleteUserHandleFunc),
//		}).Build()
//		if err != nil {
//			panic(err)
//		}
//		log.Fatal(http.ListenAndServe(":8080", r))
//	}
package routing

import (
	"net/http"

	"github.com/naoina/denco"
)

// Router represents a multiplexer for HTTP requests.
type Router struct {
	data    *denco.Router  // data stores denco router.
	indexes map[string]int // indexes are used to check whether a record exists.
	records []denco.Record // records is a list of handlers expected by denco router.
}

// Routes is an alias of []Route.
type Routes []Route

// Route is used to store information about HTTP request's handler
// including a list of allowed methods and pattern.
type Route struct {
	handler *http.HandlerFunc // HTTP request handler function.
	methods []string          // A list of allowed HTTP methods (e.g. "GET" or "POST").
	pattern string            // Pattern is a routing path for handler.
}

// NewRouter allocates and returns a new multiplexer.
func NewRouter() *Router {
	return &Router{
		indexes: map[string]int{},
	}
}

// HasMethod checks whether specific method request is allowed.
func (t *Route) HasMethod(name string) bool {
	// We are iterating through a slice of method strings
	// rather than using a map as there are only a few possible values.
	// So, hash function will require more time than a simple loop.
	for _, v := range t.methods {
		if v == name {
			return true
		}
	}
	return false
}

// ServeHTTP is used to implement http.Handler interface.
// It dispatches the request to the handler whose pattern
// most closely matches the request URL.
func (t *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h, _ := t.Handler(r)
	h.ServeHTTP(w, r)
}

// Handle registers handlers for given patterns.
// If a handler already exists for pattern, it will be overridden.
// If it exists but with another method, a new method will be added.
func (t *Router) Handle(routes Routes) {
	for _, route := range routes {
		// Check whether we have already had such route.
		index, ok := t.indexes[route.pattern]
		if ok {
			// If we haven't, add it.
			t.records = append(t.records, denco.NewRecord(route.pattern, route))
			continue
		}

		// Check whether existing route has the same handler and
		// we are just trying to add a new method.
		r := t.records[index].Value.(Route)
		if r.handler != route.handler {
			// If we aren't, override an old route.
			t.records[index] = denco.NewRecord(route.pattern, route)
			continue
		}

		// Otherwise, add all methods that haven't added yet.
		for _, v := range r.methods {
			if !route.HasMethod(v) {
				route.methods = append(route.methods, v)
			}
		}
	}
}

// Build compiles registered routes. Routes that are added after building will not
// be handled. A new call to build will be required.
func (t *Router) Build() error {
	router := denco.New()
	err := router.Build(t.records)
	if err != nil {
		return err
	}
	return nil
}

// Route allocates and returns a Route struct.
func (t *Router) Route(method, pattern string, handler http.HandlerFunc) Route {
	return Route{
		handler: &handler,
		methods: []string{method},
		pattern: pattern,
	}
}

// Handler returns the handler to use for the given request, consulting r.Method
// and r.URL.Path. It always returns a non-nil handler. If there is no registered handler
// that applies to the request, Handler returns a “page not found” handler and empty pattern.
// If there is a registered handler but requested method is not allowed,
// "method not allowed" and a pattern are returned.
func (t *Router) Handler(r *http.Request) (handler http.Handler, pattern string) {
	// Make sure we have a handler for this request.
	obj, params, found := t.data.Lookup(r.URL.Path)
	if !found {
		return http.HandlerFunc(NotFound), ""
	}

	// Check whether requested method is allowed.
	route := obj.(Route)
	if !route.HasMethod(r.Method) {
		return http.HandlerFunc(MethodNotAllowed), route.pattern
	}

	// Add parameters of request to request.Form and return a handler.
	for _, param := range params {
		r.Form.Add(param.Name, param.Value)
	}
	return route.handler, route.pattern
}

// MethodNotAllowed replies to the request with an HTTP 405 method not allowed
// error. If you want to use your own MethodNotAllowed handler, please override
// this variable.
var MethodNotAllowed = func(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
}

// NotFound replies to the request with an HTTP 404 not found error.
// NotFound is called when unknown HTTP method or a handler not found.
// If you want to use the your own NotFound handler, please overwrite this variable.
var NotFound = func(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}
<|endoftext|>


<commit_before>package format

import (
	"bufio"
	"bytes"
	"github.com/flexiant/concerto/api"
	"github.com/flexiant/concerto/testdata"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestPrintItemDomainTXT(t *testing.T) {

	assert := assert.New(t)
	domainsIn := testdata.GetDomainData()
	for _, domainIn := range *domainsIn {

		domainOut := api.GetDomainMocked(t, &domainIn)

		var b bytes.Buffer
		mockOut := bufio.NewWriter(&b)
		InitializeFormatter("text", mockOut)
		f := GetFormatter()
		assert.NotNil(f, "Formatter")

		err := f.PrintItem(*domainOut)
		assert.Nil(err, "Text formatter PrintItem error")
		mockOut.Flush()

		// TODO add more accurate parsing
		assert.Regexp("^ID:\\ *.*\n*.\n", b.String(), "JSON Output didn't match regular expression")

	}
}

// func TestPrintListDomains(t *testing.T) {
//
// 	assert := assert.New(t)
// 	domainsIn := testdata.GetDomainData()
// 	domainOut := api.GetDomainListMocked(t, domainsIn)
//
// 	var b bytes.Buffer
// 	mockOut := bufio.NewWriter(&b)
// 	InitializeFormatter("json", mockOut)
// 	f := GetFormatter()
// 	assert.NotNil(f, "Formatter")
//
// 	err := f.PrintList(domainOut)
// 	assert.Nil(err, "JSON formatter PrintItem error")
// 	mockOut.Flush()
//
// 	// TODO add more accurate parsing
// 	assert.Regexp("^\\[\\{\\\"id\\\":.*\\}\\]", b.String(), "JSON Output didn't match regular expression")
// }
//
// func TestPrintError(t *testing.T) {
//
// 	assert := assert.New(t)
//
// 	var b bytes.Buffer
// 	mockOut := bufio.NewWriter(&b)
//
// 	InitializeFormatter("json", mockOut)
// 	f := GetFormatter()
// 	assert.NotNil(f, "Formatter")
//
// 	f.PrintError("testing errors", fmt.Errorf("this is a test error %s", "TEST"))
// 	mockOut.Flush()
//
// 	assert.Regexp("^\\{\\\"type\\\":\\\"Error\\\",\\\"context\\\":\\\"testing errors\\\",\\\"message\\\":\\\"this is a test error TEST\\\"\\}", b.String(), "JSON Output didn't match regular expression")
// }
<commit_msg>test txt list formatting<commit_after>package format

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/flexiant/concerto/api"
	"github.com/flexiant/concerto/testdata"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestPrintItemDomainTXT(t *testing.T) {

	assert := assert.New(t)
	domainsIn := testdata.GetDomainData()
	for _, domainIn := range *domainsIn {

		domainOut := api.GetDomainMocked(t, &domainIn)

		var b bytes.Buffer
		mockOut := bufio.NewWriter(&b)
		InitializeFormatter("text", mockOut)
		f := GetFormatter()
		assert.NotNil(f, "Formatter")

		err := f.PrintItem(*domainOut)
		assert.Nil(err, "Text formatter PrintItem error")
		mockOut.Flush()

		// TODO add more accurate parsing
		assert.Regexp("^ID:\\ *.*\n*.\n", b.String(), "JSON Output didn't match regular expression")

	}
}

func TestPrintListDomainsTXT(t *testing.T) {

	assert := assert.New(t)
	domainsIn := testdata.GetDomainData()
	domainOut := api.GetDomainListMocked(t, domainsIn)

	var b bytes.Buffer
	mockOut := bufio.NewWriter(&b)
	InitializeFormatter("text", mockOut)
	f := GetFormatter()
	assert.NotNil(f, "Formatter")

	err := f.PrintList(*domainOut)
	assert.Nil(err, "Text formatter PrintItem error")
	mockOut.Flush()

	// TODO add more accurate parsing
	assert.Regexp(fmt.Sprintf("^ID.*\n%s.*\n.*", (*domainOut)[0].ID), b.String(), "JSON Output didn't match regular expression")
}

//
// func TestPrintError(t *testing.T) {
//
// 	assert := assert.New(t)
//
// 	var b bytes.Buffer
// 	mockOut := bufio.NewWriter(&b)
//
// 	InitializeFormatter("json", mockOut)
// 	f := GetFormatter()
// 	assert.NotNil(f, "Formatter")
//
// 	f.PrintError("testing errors", fmt.Errorf("this is a test error %s", "TEST"))
// 	mockOut.Flush()
//
// 	assert.Regexp("^\\{\\\"type\\\":\\\"Error\\\",\\\"context\\\":\\\"testing errors\\\",\\\"message\\\":\\\"this is a test error TEST\\\"\\}", b.String(), "JSON Output didn't match regular expression")
// }
<|endoftext|>