<commit_before>// Copyright 2014 The Conductor Authors. All rights reserved.
// Conductor under Apache v2. License can be found in the LICENSE file.

// Package conductor implements the core server/client pieces of the conductor protocol/system.
//
// This is a work in progress. Conductor is a scalable messaging server written using popular open source tech,
// namely WebSockets and JSON encoding. It aims to be an easy and scalable way to handling real-time connections
// for tens of thousands of clients. See the README for more information.
package conductor

import (
	"fmt"
	"github.com/daltoniam/goguid"
	"github.com/gorilla/websocket"
	"net"
	"net/http"
)

// Implement the Auth interface to satisfy authentication. If unimplemented
// Conductor will ignore authentication altogether.
type Auth interface {
	// InitalAuthHandler runs when a client first connects.
	InitalAuthHandler(r *http.Request, token string) (bool, string)

	// ChannelAuthHandler runs when a client tries to bind to a channel.
	ChannelAuthHandler(message Message, token string) bool

	// MessageAuthHandler runs when a client sends a message.
	MessageAuthHandler(message Message, token string) bool

	// MessageAuthHandler runs when checking if a client must be bound to a channel before writing to it.
	MessagAuthBoundHandler(message Message, token string) bool
}

// Implement the Notification interface to satisfy handling notifications. This interface
// is useful for storing and processing messages between clients (for history, apns, etc).
type Notification interface {
	// PersistentHandler runs on each successful message sent.
	PersistentHandler(message Message, token string)

	// BindHandler runs on each bind message sent.
	BindHandler(message Message, token string)

	// UnBindHandler runs on each unbind message sent.
	UnBindHandler(message Message, token string)

	// InviteHandler runs on each successful  invite message sent.
	InviteHandler(message Message, token string)
}

// // Implement the ServerQuery interface to satisfy handling ServerQuery messages.
// This is used for when a client needs to request something from the server e.g. (channel history, db data, client info, etc).
type ServerQuery interface {
	QueryHandler(message Message, token string) Message
}

// // Implement the PeerToPeer interface to satisfy handling peer to peer messages.
type PeerToPeer interface {
	// This is used for when a server needs to handle messages directly from another peer.
	PeerMessageHandler(message Message, peer Peer)

	// NewPeerHandler runs when a new peer connects.
	PeerConnectedHandler(peer Peer)

	// NewPeerHandler runs when a new peer disconnects.
	PeerDisconnectedHandler(peer Peer)
}

// A Server type contains all the handlers, port and authentication token
// that make up a Conductor server.
type Server struct {
	hub          hub
	Auth         Auth
	Notification Notification
	ServerQuery  ServerQuery
	PeerToPeer   PeerToPeer
	EnablePeers  bool
	Port         int
	AuthToken    string
	guid         string
}

// A Peer type represents a connection to fellow conductor servers.
type Peer struct {
	c      *connection //the connection to the remote peer.
	client *Client     //the client that can write to the remote peer.
	sName  string      //this server name to send to the peers
	Name   string      //the name of the peer
}

// CreateServer allocates and returns a new Server.
func CreateServer(port int, authToken string) Server {
	return Server{
		hub:       createHub(),
		Port:      port,
		AuthToken: authToken,
		guid:      guid.NewGUID().String(), //NewUUID Change back after testing!
	}
}

// Start starts the server to listen for incoming connections.
func (server *Server) Start() error {
	fmt.Println("server guid is:", server.guid)
	go server.hub.run()

	http.HandleFunc("/", server.websocketHandler)

	return http.ListenAndServe(fmt.Sprintf(":%d", server.Port), nil)
}

// websocketHandler handles processing new WebSocket connections to the server.
func (server *Server) websocketHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	peer := false
	peerKey := "Peer"
	tokenKey := "Token"
	name := r.Header.Get(peerKey)
	token := r.Header.Get(tokenKey)
	params := r.URL.Query()
	if len(params[peerKey]) > 0 {
		name = params[peerKey][0]
	}
	if len(params[tokenKey]) > 0 {
		token = params[tokenKey][0]
	}
	if name != "" {
		peer = true
		if server.hub.ifConnectionExist(name) || name == server.guid {
			//fmt.Println("already connected peer", name)
			http.Error(w, "", http.StatusOK) //is this the code we want to send back?
			return
		}
	}
	authStatus := true
	//check if the peers tokens match to make sure they aren't malicious clients
	if peer {
		testToken := hashToken(server.AuthToken)
		if testToken != token {
			authStatus = false
		}

	} else if server.Auth != nil {
		authStatus, name = server.Auth.InitalAuthHandler(r, token)
	}

	if authStatus {
		upgrader := websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
		}
		ws, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			fmt.Println("failed to upgrade")
			return
		}
		c := &connection{send: make(chan Message, 256), ws: ws, token: token, peer: peer, name: name}
		server.hub.register <- c
		if c.peer && server.PeerToPeer != nil {
			server.PeerToPeer.PeerConnectedHandler(Peer{c: c, sName: server.guid, Name: c.name})
		}
		go c.writePump(server)
		c.readPump(server)
	} else {
		http.Error(w, "Failed to connect, access denied", 401)
	}
}

//peer handling methods

// AddPeer adds a peer. peer param should be a valid URL/path that this
// server can connect to.
func (server *Server) AddPeer(peer string) {
	server.connectToPeer(peer)
}

//Add a pool of peers. This is a slice of all the peers that can be connected
func (server *Server) AddPeerPool(peers []string) {
	for _, p := range peers {
		server.connectToPeer(p)
	}
}

//returns all the peers
func (server *Server) AllPeers() []Peer {
	count := len(server.hub.peers)
	collect := make([]Peer, count)
	i := 0
	for _, c := range server.hub.peers {
		collect[i] = Peer{c: c, sName: server.guid, Name: c.name}
		i++
	}
	return collect
}

// connectToPeer connects to a peer server by connecting like a client, but with a special header and message.
func (server *Server) connectToPeer(peer string) {
	client, err := CreateClient(peer, server.AuthToken, server.guid)
	if err != nil {
		//fmt.Println("unable to connect to peer: ", err)
		return
	}

	//establish the 2 way connection by making the other peer connect to this server
	//fmt.Println("new peer:", peer)
	ip := server.getIP()
	selfUrl := fmt.Sprintf("ws://%s:%d", ip, server.Port)
	//fmt.Println("connect back to new peer:", selfUrl)
	client.Write(selfUrl, "", PeerBindOpCode, nil)

	//forwards messages from peers onto to the hub.
	client.ServerBind(func(msg Message) {
		if msg.OpCode == PeerOpCode {
			if server.PeerToPeer != nil {
				server.PeerToPeer.PeerMessageHandler(msg, Peer{client: &client, sName: server.guid, Name: msg.Name})
			}
		} else {
			server.hub.broadcast <- broadcastWriter{conn: nil, message: &msg, peer: true}
		}
	})
	go client.ReadLoop()
}

// getIP fetches our local IP.
func (server *Server) getIP() string {
	ipAddrs, _ := net.InterfaceAddrs()
	for _, ipAddr := range ipAddrs {
		ip, _, _ := net.ParseCIDR(ipAddr.String())
		if !ip.IsLoopback() && ip.To4() != nil {
			return ip.String()
		}
	}
	return ""
}

//send the peer a message
func (p *Peer) SendMessage(body, context string, additional interface{}) {
	if p.c != nil {
		p.c.send <- Message{Name: p.sName, Body: body, ChannelName: context, OpCode: PeerOpCode, Additional: additional}
	} else if p.client != nil {
		p.client.Write(body, context, PeerOpCode, additional)
	}
}
<commit_msg>cleaned up println<commit_after>// Copyright 2014 The Conductor Authors. All rights reserved.
// Conductor under Apache v2. License can be found in the LICENSE file.

// Package conductor implements the core server/client pieces of the conductor protocol/system.
//
// This is a work in progress. Conductor is a scalable messaging server written using popular open source tech,
// namely WebSockets and JSON encoding. It aims to be an easy and scalable way to handling real-time connections
// for tens of thousands of clients. See the README for more information.
package conductor

import (
	"fmt"
	"github.com/daltoniam/goguid"
	"github.com/gorilla/websocket"
	"net"
	"net/http"
)

// Implement the Auth interface to satisfy authentication. If unimplemented
// Conductor will ignore authentication altogether.
type Auth interface {
	// InitalAuthHandler runs when a client first connects.
	InitalAuthHandler(r *http.Request, token string) (bool, string)

	// ChannelAuthHandler runs when a client tries to bind to a channel.
	ChannelAuthHandler(message Message, token string) bool

	// MessageAuthHandler runs when a client sends a message.
	MessageAuthHandler(message Message, token string) bool

	// MessageAuthHandler runs when checking if a client must be bound to a channel before writing to it.
	MessagAuthBoundHandler(message Message, token string) bool
}

// Implement the Notification interface to satisfy handling notifications. This interface
// is useful for storing and processing messages between clients (for history, apns, etc).
type Notification interface {
	// PersistentHandler runs on each successful message sent.
	PersistentHandler(message Message, token string)

	// BindHandler runs on each bind message sent.
	BindHandler(message Message, token string)

	// UnBindHandler runs on each unbind message sent.
	UnBindHandler(message Message, token string)

	// InviteHandler runs on each successful  invite message sent.
	InviteHandler(message Message, token string)
}

// // Implement the ServerQuery interface to satisfy handling ServerQuery messages.
// This is used for when a client needs to request something from the server e.g. (channel history, db data, client info, etc).
type ServerQuery interface {
	QueryHandler(message Message, token string) Message
}

// // Implement the PeerToPeer interface to satisfy handling peer to peer messages.
type PeerToPeer interface {
	// This is used for when a server needs to handle messages directly from another peer.
	PeerMessageHandler(message Message, peer Peer)

	// NewPeerHandler runs when a new peer connects.
	PeerConnectedHandler(peer Peer)

	// NewPeerHandler runs when a new peer disconnects.
	PeerDisconnectedHandler(peer Peer)
}

// A Server type contains all the handlers, port and authentication token
// that make up a Conductor server.
type Server struct {
	hub          hub
	Auth         Auth
	Notification Notification
	ServerQuery  ServerQuery
	PeerToPeer   PeerToPeer
	EnablePeers  bool
	Port         int
	AuthToken    string
	guid         string
}

// A Peer type represents a connection to fellow conductor servers.
type Peer struct {
	c      *connection //the connection to the remote peer.
	client *Client     //the client that can write to the remote peer.
	sName  string      //this server name to send to the peers
	Name   string      //the name of the peer
}

// CreateServer allocates and returns a new Server.
func CreateServer(port int, authToken string) Server {
	return Server{
		hub:       createHub(),
		Port:      port,
		AuthToken: authToken,
		guid:      guid.NewUUID().String(),
	}
}

// Start starts the server to listen for incoming connections.
func (server *Server) Start() error {
	//fmt.Println("server guid is:", server.guid)
	go server.hub.run()

	http.HandleFunc("/", server.websocketHandler)

	return http.ListenAndServe(fmt.Sprintf(":%d", server.Port), nil)
}

// websocketHandler handles processing new WebSocket connections to the server.
func (server *Server) websocketHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	peer := false
	peerKey := "Peer"
	tokenKey := "Token"
	name := r.Header.Get(peerKey)
	token := r.Header.Get(tokenKey)
	params := r.URL.Query()
	if len(params[peerKey]) > 0 {
		name = params[peerKey][0]
	}
	if len(params[tokenKey]) > 0 {
		token = params[tokenKey][0]
	}
	if name != "" {
		peer = true
		if server.hub.ifConnectionExist(name) || name == server.guid {
			//fmt.Println("already connected peer", name)
			http.Error(w, "", http.StatusOK) //is this the code we want to send back?
			return
		}
	}
	authStatus := true
	//check if the peers tokens match to make sure they aren't malicious clients
	if peer {
		testToken := hashToken(server.AuthToken)
		if testToken != token {
			authStatus = false
		}

	} else if server.Auth != nil {
		authStatus, name = server.Auth.InitalAuthHandler(r, token)
	}

	if authStatus {
		upgrader := websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
		}
		ws, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			//fmt.Println("failed to upgrade")
			http.Error(w, "failed to upgrade", 500)
			return
		}
		c := &connection{send: make(chan Message, 256), ws: ws, token: token, peer: peer, name: name}
		server.hub.register <- c
		if c.peer && server.PeerToPeer != nil {
			server.PeerToPeer.PeerConnectedHandler(Peer{c: c, sName: server.guid, Name: c.name})
		}
		go c.writePump(server)
		c.readPump(server)
	} else {
		http.Error(w, "Failed to connect, access denied", 401)
	}
}

//peer handling methods

// AddPeer adds a peer. peer param should be a valid URL/path that this
// server can connect to.
func (server *Server) AddPeer(peer string) {
	server.connectToPeer(peer)
}

//Add a pool of peers. This is a slice of all the peers that can be connected
func (server *Server) AddPeerPool(peers []string) {
	for _, p := range peers {
		server.connectToPeer(p)
	}
}

//returns all the peers
func (server *Server) AllPeers() []Peer {
	count := len(server.hub.peers)
	collect := make([]Peer, count)
	i := 0
	for _, c := range server.hub.peers {
		collect[i] = Peer{c: c, sName: server.guid, Name: c.name}
		i++
	}
	return collect
}

// connectToPeer connects to a peer server by connecting like a client, but with a special header and message.
func (server *Server) connectToPeer(peer string) {
	client, err := CreateClient(peer, server.AuthToken, server.guid)
	if err != nil {
		//fmt.Println("unable to connect to peer: ", err)
		return
	}

	//establish the 2 way connection by making the other peer connect to this server
	//fmt.Println("new peer:", peer)
	ip := server.getIP()
	selfUrl := fmt.Sprintf("ws://%s:%d", ip, server.Port)
	//fmt.Println("connect back to new peer:", selfUrl)
	client.Write(selfUrl, "", PeerBindOpCode, nil)

	//forwards messages from peers onto to the hub.
	client.ServerBind(func(msg Message) {
		if msg.OpCode == PeerOpCode {
			if server.PeerToPeer != nil {
				server.PeerToPeer.PeerMessageHandler(msg, Peer{client: &client, sName: server.guid, Name: msg.Name})
			}
		} else {
			server.hub.broadcast <- broadcastWriter{conn: nil, message: &msg, peer: true}
		}
	})
	go client.ReadLoop()
}

// getIP fetches our local IP.
func (server *Server) getIP() string {
	ipAddrs, _ := net.InterfaceAddrs()
	for _, ipAddr := range ipAddrs {
		ip, _, _ := net.ParseCIDR(ipAddr.String())
		if !ip.IsLoopback() && ip.To4() != nil {
			return ip.String()
		}
	}
	return ""
}

//send the peer a message
func (p *Peer) SendMessage(body, context string, additional interface{}) {
	if p.c != nil {
		p.c.send <- Message{Name: p.sName, Body: body, ChannelName: context, OpCode: PeerOpCode, Additional: additional}
	} else if p.client != nil {
		p.client.Write(body, context, PeerOpCode, additional)
	}
}
<|endoftext|>


<commit_before>package main

import (
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
  "github.com/codegangsta/martini"
  "encoding/json"
  "net/http"
)

func main() {
  m := martini.Classic()

  db, err := sql.Open("mysql", "root@127.0.0.1/ops")
  if err != nil { panic(err) }
  defer db.Close()

  m.Map(db)

  //All fake data
  schoolOne := School{
    Id: 1,
    Name: "Millard North High School",
    CountyId: 1,
    DistrictId: 1,
    Latitude: -35.22,
    Longitude: 45.12,
  }
  schoolTwo := School{
    Id: 2,
    Name: "Millard South High School",
    CountyId: 1,
    DistrictId: 1,
    Latitude: -35.22,
    Longitude: 45.12,
  }
  schools := []School{schoolOne, schoolTwo}

  entry2012 := DistrictYear{
    EnrollmentSize: 15,
    District: District{
      Id: 15,
      Name: "OPS",
      Latitude: 72.12345,
      Longitude: 45.215,
    },
  }

  schoolYearOne := SchoolYear{
    EnrollmentSize: 55,
    School: schoolOne,
  }
  district66 := SchoolsByYear{
    Year: "2012-2013",
    Schools: []SchoolYear{
      schoolYearOne,
    },
  }

  allDistricts := []DistrictsByYear{
    DistrictsByYear{
      Year: "2012-2013",
      Districts: []DistrictYear{entry2012},
    },
    DistrictsByYear{
      Year: "2011-2012",
      Districts: []DistrictYear{entry2012},
    },
  }

  edisonElementary := SchoolWithEnrollment{
    School: schoolOne,
    EnrollmentByYear: []EnrollmentByYear{
      EnrollmentByYear{
        Year: "2012-2013",
        Teachers: 55,
        Students: 3000,
        GradeEnrollment: []GradeEnrollment{
          GradeEnrollment{
            Grade: "6th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "5th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "4th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "3th",
            Enrollment: 544,
          },
        },
      },
      EnrollmentByYear{
        Year: "2011-2012",
        Teachers: 55,
        Students: 3000,
        GradeEnrollment: []GradeEnrollment{
          GradeEnrollment{
            Grade: "6th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "5th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "4th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "3th",
            Enrollment: 544,
          },
        },
      },
    },
  }

  //Routes
  m.Get("/schools", func(res http.ResponseWriter) string {
    return render(res, schools)
  })

  m.Get("/districts", func(res http.ResponseWriter) string {
    return render(res, allDistricts)
  })

  m.Get("/districts/:id", func(res http.ResponseWriter) string {
    return render(res, district66)
  })

  m.Get("/school/:id", func(res http.ResponseWriter, params martini.Params) string {
    return render(res, edisonElementary)
  })

  m.Run()
}

func render(res http.ResponseWriter, data interface{}) string {
  thing, err := json.Marshal(data)
  if err != nil { panic(err) }
  return asJson(res, thing)
}

func asJson(res http.ResponseWriter, data []byte) string {
  res.Header().Set("Content-Type", "application/json")
  res.Header().Set("Access-Control-Allow-Origin", "*")
  return string(data[:])
}

type School struct {
  Id          int64
  Name        string `sql:"size:255"`
  CountyId    int64
  DistrictId  int64
  Latitude    float64
  Longitude   float64
}

type SchoolsByYear struct {
  Year        string
  Schools     []SchoolYear
}

type SchoolYear struct {
  EnrollmentSize  int64
  School          School
}

type District struct {
  Id              int64
  Name            string
  Latitude        float64
  Longitude       float64
}

type SchoolWithEnrollment struct {
  School            School
  EnrollmentByYear  []EnrollmentByYear
}

type EnrollmentByYear struct {
  Year          string
  Teachers      int64
  Students      int64
  GradeEnrollment []GradeEnrollment
}

type GradeEnrollment struct {
  Grade         string
  Enrollment    int64
}

type DistrictsByYear struct {
  Year      string
  Districts []DistrictYear
}

type DistrictYear struct {
  EnrollmentSize  int64
  District        District
}
<commit_msg>changing api willy nilly<commit_after>package main

import (
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
  "github.com/codegangsta/martini"
  "encoding/json"
  "net/http"
)

func main() {
  m := martini.Classic()

  db, err := sql.Open("mysql", "root@127.0.0.1/ops")
  if err != nil { panic(err) }
  defer db.Close()

  m.Map(db)

  //All fake data
  schoolOne := School{
    Id: 1,
    Name: "Millard North High School",
    CountyId: 1,
    DistrictId: 1,
    Latitude: -35.22,
    Longitude: 45.12,
  }
  schoolTwo := School{
    Id: 2,
    Name: "Millard South High School",
    CountyId: 1,
    DistrictId: 1,
    Latitude: -35.22,
    Longitude: 45.12,
  }
  schools := []School{schoolOne, schoolTwo}

  entry2012 := DistrictYear{
    EnrollmentSize: 15,
    District: District{
      Id: 15,
      Name: "OPS",
      Latitude: 72.12345,
      Longitude: 45.215,
    },
  }

  schoolYearOne := SchoolYear{
    EnrollmentSize: 55,
    School: schoolOne,
  }
  district66 := SchoolsByYear{
    Year: "2012-2013",
    Schools: []SchoolYear{
      schoolYearOne,
    },
  }

  allDistricts := []DistrictsByYear{
    DistrictsByYear{
      Year: "2012-2013",
      Districts: []DistrictYear{entry2012},
    },
    DistrictsByYear{
      Year: "2011-2012",
      Districts: []DistrictYear{entry2012},
    },
  }

  edisonElementary := SchoolWithEnrollment{
    School: schoolOne,
    EnrollmentByYear: []EnrollmentByYear{
      EnrollmentByYear{
        Year: "2012-2013",
        Teachers: 55,
        Students: 3000,
        GradeEnrollment: []GradeEnrollment{
          GradeEnrollment{
            Grade: "6th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "5th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "4th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "3th",
            Enrollment: 544,
          },
        },
      },
      EnrollmentByYear{
        Year: "2011-2012",
        Teachers: 55,
        Students: 3000,
        GradeEnrollment: []GradeEnrollment{
          GradeEnrollment{
            Grade: "6th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "5th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "4th",
            Enrollment: 544,
          },
          GradeEnrollment{
            Grade: "3th",
            Enrollment: 544,
          },
        },
      },
    },
  }

  //Routes
  m.Get("/schools", func(res http.ResponseWriter) string {
    return render(res, schools)
  })

  m.Get("/districts", func(res http.ResponseWriter) string {
    return render(res, allDistricts)
  })

  m.Get("/district/:id", func(res http.ResponseWriter) string {
    return render(res, district66)
  })

  m.Get("/school/:id", func(res http.ResponseWriter, params martini.Params) string {
    return render(res, edisonElementary)
  })

  m.Run()
}

func render(res http.ResponseWriter, data interface{}) string {
  thing, err := json.Marshal(data)
  if err != nil { panic(err) }
  return asJson(res, thing)
}

func asJson(res http.ResponseWriter, data []byte) string {
  res.Header().Set("Content-Type", "application/json")
  res.Header().Set("Access-Control-Allow-Origin", "*")
  return string(data[:])
}

type School struct {
  Id          int64
  Name        string `sql:"size:255"`
  CountyId    int64
  DistrictId  int64
  Latitude    float64
  Longitude   float64
}

type SchoolsByYear struct {
  Year        string
  Schools     []SchoolYear
}

type SchoolYear struct {
  EnrollmentSize  int64
  School          School
}

type District struct {
  Id              int64
  Name            string
  Latitude        float64
  Longitude       float64
}

type SchoolWithEnrollment struct {
  School            School
  EnrollmentByYear  []EnrollmentByYear
}

type EnrollmentByYear struct {
  Year          string
  Teachers      int64
  Students      int64
  GradeEnrollment []GradeEnrollment
}

type GradeEnrollment struct {
  Grade         string
  Enrollment    int64
}

type DistrictsByYear struct {
  Year      string
  Districts []DistrictYear
}

type DistrictYear struct {
  EnrollmentSize  int64
  District        District
}
<|endoftext|>


<commit_before>package wedge

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"path/filepath"
	"sort"
	"time"
)

// appServer is our server instance which holds the ServeHTTP method
// so that it satisfies the http.Server interface.
type appServer struct {
	port       string
	routes     []*url
	timeout    time.Duration
	cache_map  *safeMap
	handler404 view
	stat_map   *safeMap
}

// appServer constructor
func NewAppServer(port string, timeout time.Duration) *appServer {
	return &appServer{
		port:      port,
		routes:    make([]*url, 0),
		timeout:   timeout,
		cache_map: NewSafeMap(),
	}
}

// Attaches more *urls to the Routes slice on the appServer value
func (App *appServer) AddURLs(patterns ...*url) {
	for _, url := range patterns {
		App.routes = append(App.routes, url)
	}
}

// EnableStatTracking does exactly what it says on the tin
//
// EnableStatTracking creates a NewSafeMap under the stat_map field which will
// then be used to increment and aggregate hits to URLs.
//
// This function will append a new *url onto the associated appServer. The url
// which this is under is ^/statistics/?$.
func (App *appServer) EnableStatTracking() {
	App.stat_map = NewSafeMap()

	staturl := makeurl("^/statistics/?$", "Statistics", func(req *http.Request) (string, int) {

		rawdata, ok := App.stat_map.Do(func(m freemap) interface{} {
			// we could return m here but that would mean we've broken the
			// reason why we made the map safe in the first place.
			outstr := `<!DOCTYPE html><html><table border="2">`
			outstr += `<tr><th>URL</th><th>Hits</th></tr>`
			var urllist []string
			for key, _ := range m {
				urllist = append(urllist, key.(string))
			}
			sort.Strings(urllist)
			for _, key := range urllist {
				outstr += fmt.Sprintf("
%s	", key)
				outstr += fmt.Sprintf("%d
", m[key].(int))
			}
			outstr += `

					   `
			return outstr
		})

		if !ok {
			return "Failure getting data", 500
		}
		return rawdata.(string), 200

	}, HTML, 0)
	App.routes = append(App.routes, staturl)
}

// incrementStats is a non-blocking method to increment a page counter
// for individual routes.
func (App *appServer) incrementStats(k string) {
	if App.stat_map == nil {
		panic("Cannot increment statistics when it has not been enabled!")
	}

	// create a goroutine which sends a function literal to the async
	// map which tries to increment the value under the k string.
	go App.stat_map.Do(func(m freemap) interface{} {
		val, ok := m[k]
		if ok {
			val, ok := val.(int)
			if ok {
				val++
				m[k] = val
			}
		} else {
			m[k] = 1
		}
		return true
	})
}

// Sets the 404 Handler for the appServer to fn.
func (App *appServer) Handler404(fn view) {
	App.handler404 = fn
}

// This is the main 'event loop' for the web server. All requests are
// sent to this handler, which checks the incoming request against
// all the routes we have setup if it finds a match it will invoke
// the handler which is attached to that match.
//
// If somehow the URL it finds has been created with a non-existant
// handler type it will panic.
func (App *appServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	request := req.URL.Path

	for _, route := range App.routes {
		matches := route.match.FindAllStringSubmatch(request, 1)
		if len(matches) > 0 {
			log.Println("Request:", route.name, request)

			if App.stat_map != nil {
				App.incrementStats(request)
			}

			resp, status := App.getResponse(route, req)

			if status == 404 {
				App.handle404req(w, req)
				return
			}

			switch route.viewtype {
			case HTML:
				io.WriteString(w, resp)
				return
			case JSON:
				w.Header().Set("Content-type", "application/json")
				json.NewEncoder(w).Encode(map[string]string{
					"message": resp,
				})
				return
			case STATIC:
				if w.Header().Get("Content-Type") == "" {
					reqstr := req.URL.Path[len(route.rawre):]
					ctype := mime.TypeByExtension(filepath.Ext(reqstr))
					w.Header().Set("Content-Type", ctype)
				}
				io.WriteString(w, resp)
				return
			case ICON:
				w.Header().Set("Content-Type", "image/x-icon")
				io.WriteString(w, resp)
				return
			default:
				panic("Unknown handler type!")
			}
		}
	}
	App.handle404req(w, req)
	return
}

// handle404req checks if the 404 handler is a custom one and uses that, if not,
// it uses the built-in NotFound function.
func (App *appServer) handle404req(w http.ResponseWriter, req *http.Request) {
	log.Println("404 on path:", req.URL.Path)

	if App.stat_map != nil {
		App.incrementStats("404" + req.URL.Path)
	}

	w.WriteHeader(404)

	if App.handler404 != nil {
		resp, _ := App.handler404(req)
		io.WriteString(w, resp)
		return
	} else {
		http.NotFound(w, req)
		return
	}
}

// getResponse checks the *url's cache_duration, if the cache duration
// is zero. Then we never cache the response. Otherwise, we check to
// see if the cache_duration has passed by reading the timeout channel
// if so, we run the URL handler associated with the route and store it's
// new response value. We then store the response in the cache_map and
// return it to the client.
//
// Accessing the cache_map from multiple threads is safe. There are two
// implementations of a safe map included with this library. One is sync'd
// with channels (safeMap) and the other is sync'd with a mutex lock
// (lockMap). We currently use the safeMap.
func (App *appServer) getResponse(route *url, req *http.Request) (string, int) {

	if route.cache_duration == 0 {
		return route.handler(req)
	}

	select {
	case <-route.timeout:
		// reset the timeout timer
		go func(d time.Duration, ch chan bool) {
			log.Println("Timed out")
			f := time.After(d * TIMEOUT)
			<-f
			go func() {
				ch <- true
			}()
		}(route.cache_duration, route.timeout)
		// get the new response and cache it in the map
		resp, err := route.handler(req)
		if !App.cache_map.Insert(req.URL.Path, resp) {
			panic("Inserting into cache_map failure!")
		}
		return resp, err
	default:
		resp, ok := App.cache_map.Find(req.URL.Path).(string)
		var status int
		if !ok {
			resp, status = route.handler(req)
		}
		if status != 404 {
			if !App.cache_map.Insert(req.URL.Path, resp) {
				panic("Inserting into cache_map failure!")
			}
		}
		return resp, status
	}
	panic("unreachable")
}

// Starts the server running on PORT `port` with the timeout duration
func (App *appServer) Run() {
	server := http.Server{
		Addr:        ":" + App.port,
		Handler:     App,
		ReadTimeout: App.timeout * time.Second,
	}
	fmt.Printf("Serving on PORT: %s\n", App.port)
	err := server.ListenAndServe()
	if err != nil {
		fmt.Println(err)
	}
}
<commit_msg>If the request for the favicon.ico results in a 404. We send nothing back instead of a 404 response<commit_after>package wedge

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"path/filepath"
	"sort"
	"time"
)

// appServer is our server instance which holds the ServeHTTP method
// so that it satisfies the http.Server interface.
type appServer struct {
	port       string
	routes     []*url
	timeout    time.Duration
	cache_map  *safeMap
	handler404 view
	stat_map   *safeMap
}

// appServer constructor
func NewAppServer(port string, timeout time.Duration) *appServer {
	return &appServer{
		port:      port,
		routes:    make([]*url, 0),
		timeout:   timeout,
		cache_map: NewSafeMap(),
	}
}

// Attaches more *urls to the Routes slice on the appServer value
func (App *appServer) AddURLs(patterns ...*url) {
	for _, url := range patterns {
		App.routes = append(App.routes, url)
	}
}

// EnableStatTracking does exactly what it says on the tin
//
// EnableStatTracking creates a NewSafeMap under the stat_map field which will
// then be used to increment and aggregate hits to URLs.
//
// This function will append a new *url onto the associated appServer. The url
// which this is under is ^/statistics/?$.
func (App *appServer) EnableStatTracking() {
	App.stat_map = NewSafeMap()

	staturl := makeurl("^/statistics/?$", "Statistics", func(req *http.Request) (string, int) {

		rawdata, ok := App.stat_map.Do(func(m freemap) interface{} {
			// we could return m here but that would mean we've broken the
			// reason why we made the map safe in the first place.
			outstr := `<!DOCTYPE html><html><table border="2">`
			outstr += `<tr><th>URL</th><th>Hits</th></tr>`
			var urllist []string
			for key, _ := range m {
				urllist = append(urllist, key.(string))
			}
			sort.Strings(urllist)
			for _, key := range urllist {
				outstr += fmt.Sprintf("
%s	", key)
				outstr += fmt.Sprintf("%d
", m[key].(int))
			}
			outstr += `

					   `
			return outstr
		})

		if !ok {
			return "Failure getting data", 500
		}
		return rawdata.(string), 200

	}, HTML, 0)
	App.routes = append(App.routes, staturl)
}

// incrementStats is a non-blocking method to increment a page counter
// for individual routes.
func (App *appServer) incrementStats(k string) {
	if App.stat_map == nil {
		panic("Cannot increment statistics when it has not been enabled!")
	}

	// create a goroutine which sends a function literal to the async
	// map which tries to increment the value under the k string.
	go App.stat_map.Do(func(m freemap) interface{} {
		val, ok := m[k]
		if ok {
			val, ok := val.(int)
			if ok {
				val++
				m[k] = val
			}
		} else {
			m[k] = 1
		}
		return true
	})
}

// Sets the 404 Handler for the appServer to fn.
func (App *appServer) Handler404(fn view) {
	App.handler404 = fn
}

// This is the main 'event loop' for the web server. All requests are
// sent to this handler, which checks the incoming request against
// all the routes we have setup if it finds a match it will invoke
// the handler which is attached to that match.
//
// If somehow the URL it finds has been created with a non-existant
// handler type it will panic.
func (App *appServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	request := req.URL.Path

	for _, route := range App.routes {
		matches := route.match.FindAllStringSubmatch(request, 1)
		if len(matches) > 0 {
			log.Println("Request:", route.name, request)

			if App.stat_map != nil {
				App.incrementStats(request)
			}

			resp, status := App.getResponse(route, req)

			if status == 404 {
				App.handle404req(w, req)
				return
			}

			switch route.viewtype {
			case HTML:
				io.WriteString(w, resp)
				return
			case JSON:
				w.Header().Set("Content-type", "application/json")
				json.NewEncoder(w).Encode(map[string]string{
					"message": resp,
				})
				return
			case STATIC:
				if w.Header().Get("Content-Type") == "" {
					reqstr := req.URL.Path[len(route.rawre):]
					ctype := mime.TypeByExtension(filepath.Ext(reqstr))
					w.Header().Set("Content-Type", ctype)
				}
				io.WriteString(w, resp)
				return
			case ICON:
				w.Header().Set("Content-Type", "image/x-icon")
				io.WriteString(w, resp)
				return
			default:
				panic("Unknown handler type!")
			}
		}
	}
	App.handle404req(w, req)
	return
}

// handle404req checks if the 404 handler is a custom one and uses that, if not,
// it uses the built-in NotFound function.
func (App *appServer) handle404req(w http.ResponseWriter, req *http.Request) {

	log.Println("404 on path:", req.URL.Path)
	if App.stat_map != nil {
		App.incrementStats("404" + req.URL.Path)
	}
	if req.URL.Path == "/favicon.ico" { return }

	w.WriteHeader(404)
	if App.handler404 != nil {
		resp, _ := App.handler404(req)
		io.WriteString(w, resp)
		return
	} else {
		http.NotFound(w, req)
		return
	}
}

// getResponse checks the *url's cache_duration, if the cache duration
// is zero. Then we never cache the response. Otherwise, we check to
// see if the cache_duration has passed by reading the timeout channel
// if so, we run the URL handler associated with the route and store it's
// new response value. We then store the response in the cache_map and
// return it to the client.
//
// Accessing the cache_map from multiple threads is safe. There are two
// implementations of a safe map included with this library. One is sync'd
// with channels (safeMap) and the other is sync'd with a mutex lock
// (lockMap). We currently use the safeMap.
func (App *appServer) getResponse(route *url, req *http.Request) (string, int) {

	if route.cache_duration == 0 {
		return route.handler(req)
	}

	select {
	case <-route.timeout:
		// reset the timeout timer
		go func(d time.Duration, ch chan bool) {
			log.Println("Timed out")
			f := time.After(d * TIMEOUT)
			<-f
			go func() {
				ch <- true
			}()
		}(route.cache_duration, route.timeout)
		// get the new response and cache it in the map
		resp, err := route.handler(req)
		if !App.cache_map.Insert(req.URL.Path, resp) {
			panic("Inserting into cache_map failure!")
		}
		return resp, err
	default:
		resp, ok := App.cache_map.Find(req.URL.Path).(string)
		var status int
		if !ok {
			resp, status = route.handler(req)
		}
		if status != 404 {
			if !App.cache_map.Insert(req.URL.Path, resp) {
				panic("Inserting into cache_map failure!")
			}
		}
		return resp, status
	}
	panic("unreachable")
}

// Starts the server running on PORT `port` with the timeout duration
func (App *appServer) Run() {
	server := http.Server{
		Addr:        ":" + App.port,
		Handler:     App,
		ReadTimeout: App.timeout * time.Second,
	}
	fmt.Printf("Serving on PORT: %s\n", App.port)
	err := server.ListenAndServe()
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>package quic

import (
	"github.com/ami-GS/gQUIC/utils"
)

type Server struct {
	St              *Transport
	Clients         map[uint64]*Client
	SupportVersions []uint32
}

func NewServer() (*Server, error) {
	st, err := NewTransport("cert", "key")
	if err != nil {
		return nil, err
	}
	return &Server{
		St:      st,
		Clients: make(map[uint64]*Client),
	}, nil
}

func (self *Server) ListenAndServe(addPair string) error {
	udpAddr, err := utils.ParseAddressPair(addPair)
	if err != nil {
		return err
	}

	err = self.St.Listen(udpAddr)
	if err != nil {
		return err
	}

	for {
		p, _, rAddr, err := self.St.Recv()
		if err != nil {
			break
		}
		client, ok := self.Clients[p.GetConnectionID()]
		if ok {
			client.RecvChan <- p
		} else {
			client, err = NewClient(true) // true stands for server side object
			if err != nil {
				break
			}
			client.Conn, err = NewConnection(rAddr)
			client.Conn.Transport = self.St
			client.Conn.ConnectionID = p.GetConnectionID()
			if err != nil {
				break
			}
			self.Clients[p.GetConnectionID()] = client
			go client.ReadLoop()
			client.RecvChan <- p
		}
	}

	return err
}

func (self *Server) FramePacket(frames []*Frame) {}

func (self *Server) PublicResetPacket() {}

func (self *Server) VersionNegotiationPacket() {}

func (self *Server) checkProposedVersion(prppVersion uint32) bool {
	// compare the proposed version to lists
	return true
}
<commit_msg>refactoring<commit_after>package quic

import (
	"github.com/ami-GS/gQUIC/utils"
)

type Server struct {
	St              *Transport
	Clients         map[uint64]*Client
	SupportVersions []uint32
}

func NewServer() (*Server, error) {
	st, err := NewTransport("cert", "key")
	if err != nil {
		return nil, err
	}
	return &Server{
		St:      st,
		Clients: make(map[uint64]*Client),
	}, nil
}

func (self *Server) ListenAndServe(addPair string) error {
	udpAddr, err := utils.ParseAddressPair(addPair)
	if err != nil {
		return err
	}

	err = self.St.Listen(udpAddr)
	if err != nil {
		return err
	}

	for {
		p, _, rAddr, err := self.St.Recv()
		if err != nil {
			panic(err)
		}
		client, ok := self.Clients[p.GetConnectionID()]
		if !ok {
			client, err = NewClient(true) // true stands for server side object
			if err != nil {
				panic(err)
			}
			// TODO : need to be able to set initial windowsize
			client.Conn, err = NewConnection(rAddr, -1)
			if err != nil {
				panic(err)
			}
			client.Conn.Transport = self.St
			client.Conn.ConnectionID = p.GetConnectionID()
			self.Clients[p.GetConnectionID()] = client
			go client.ReadLoop()
		}
		client.RecvChan <- p
	}

	return err
}

func (self *Server) FramePacket(frames []*Frame) {}

func (self *Server) PublicResetPacket() {}

func (self *Server) VersionNegotiationPacket() {}

func (self *Server) checkProposedVersion(prppVersion uint32) bool {
	// compare the proposed version to lists
	return true
}
<|endoftext|>


<commit_before>package dhcp4server

import (
	"bytes"
	"errors"
	"github.com/d2g/dhcp4"
	"github.com/d2g/dhcp4server/leasepool"
	"log"
	"net"
	"time"
)

/*
 * The DHCP Server Structure
 */
type Server struct {
	Configuration *Configuration      //Server Configuration
	LeasePool     leasepool.LeasePool //Lease Pool Manager

	//Used to Gracefully Close the Server
	shutdown bool

	//Listeners & Response Connection.
	inbound *net.UDPConn

	outbound *net.UDPConn
}

/*
 * Start The DHCP Server
 */
func (this *Server) ListenAndServe() error {
	var err error

	inboundAddress := net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}
	this.inbound, err = net.ListenUDP("udp4", &inboundAddress)
	if err != nil {
		return err
	}
	defer this.inbound.Close()

	outboundAddress := net.UDPAddr{IP: net.IPv4bcast, Port: 68}
	this.outbound, err = net.DialUDP("udp4", nil, &outboundAddress)
	if err != nil {
		return err
	}
	defer this.outbound.Close()

	//Make Our Buffer (Max Buffer is 574) "I believe this 576 size comes from RFC 791" - Random Mailing list quote of the day.
	buffer := make([]byte, 576)

	for {
	ListenForDHCPPackets:
		if this.shutdown {
			return nil
		}

		//Set Read Deadline
		this.inbound.SetReadDeadline(time.Now().Add(time.Second))
		// Read Packet
		n, source, err := this.inbound.ReadFrom(buffer)
		if err != nil {

			switch v := err.(type) {
			case *net.OpError:
				if v.Timeout() {
					goto ListenForDHCPPackets
				}
			case *net.AddrError:
				if v.Timeout() {
					goto ListenForDHCPPackets
				}
			case *net.UnknownNetworkError:
				if v.Timeout() {
					goto ListenForDHCPPackets
				}
			}

			log.Println("Error:" + err.Error())
			return err
		}

		//We should ignore some requests
		//It shouldn't be possible to ignore IP's because they shouldn't have them as we're the DHCP server.
		//However, they can have i.e. if you're the client & server :S.
		for _, ipToIgnore := range this.Configuration.IgnoreIPs {
			if ipToIgnore.Equal(source.(*net.UDPAddr).IP) {
				log.Println("Ignoring DHCP Request From IP:" + ipToIgnore.String())
				continue
			}
		}

		packet := dhcp4.Packet(buffer[:n])

		//We can ignore hardware addresses.
		//Usefull for ignoring a range of hardware addresses
		for _, hardwareAddressToIgnore := range this.Configuration.IgnoreHardwareAddress {
			if bytes.Equal(hardwareAddressToIgnore, packet.CHAddr()) {
				log.Println("Ignoring DHCP Request From Hardware Address:" + hardwareAddressToIgnore.String())
				continue
			}
		}

		log.Printf("Debug: Packet Received:%v\n", packet)
		log.Printf("Debug: Packet Received ID:%v\n", packet.XId())
		log.Printf("Debug: Packet Options:%v\n", packet.ParseOptions())
		log.Printf("Debug: Packet Client IP : %v\n", packet.CIAddr().String())
		log.Printf("Debug: Packet Your IP   : %v\n", packet.YIAddr().String())
		log.Printf("Debug: Packet Server IP : %v\n", packet.SIAddr().String())
		log.Printf("Debug: Packet Gateway IP: %v\n", packet.GIAddr().String())
		log.Printf("Debug: Packet Client Mac: %v\n", packet.CHAddr().String())

		//We need to stop butting in with other servers.
		if packet.SIAddr().Equal(net.IPv4(0, 0, 0, 0)) || packet.SIAddr().Equal(net.IP{}) || packet.SIAddr().Equal(this.Configuration.IP) {

			returnPacket, err := this.ServeDHCP(packet)
			if err != nil {
				log.Println("Error Serving DHCP:" + err.Error())
				return err
			}

			log.Printf("Debug: Packet Returned:%v\n", returnPacket)
			log.Printf("Debug: Packet Returned ID:%v\n", returnPacket.XId())
			log.Printf("Debug: Packet Options:%v\n", returnPacket.ParseOptions())
			log.Printf("Debug: Packet Client IP : %v\n", returnPacket.CIAddr().String())
			log.Printf("Debug: Packet Your IP   : %v\n", returnPacket.YIAddr().String())
			log.Printf("Debug: Packet Server IP : %v\n", returnPacket.SIAddr().String())
			log.Printf("Debug: Packet Gateway IP: %v\n", returnPacket.GIAddr().String())
			log.Printf("Debug: Packet Client Mac: %v\n", returnPacket.CHAddr().String())

			if len(packet) > 0 {
				_, err := this.outbound.Write(returnPacket)
				if err != nil {
					log.Println("Error Writing:" + err.Error())
					return err
				}
			}
		}

	}
}

func (this *Server) ServeDHCP(packet dhcp4.Packet) (dhcp4.Packet, error) {
	packetOptions := packet.ParseOptions()

	switch dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]) {
	case dhcp4.Discover:

		//Discover Received from client
		//Lets get the lease we're going to send them
		found, lease, err := this.GetLease(packet)
		if err != nil {
			return dhcp4.Packet{}, err
		}

		if !found {
			log.Println("It Looks Like Our Leases Are Depleted...")
			return dhcp4.Packet{}, nil
		}

		offerPacket := this.OfferPacket(packet)
		offerPacket.SetYIAddr(lease.IP)

		//Sort out the packet options
		log.Printf("%v\n", packetOptions[dhcp4.OptionParameterRequestList])

		offerPacket.PadToMinSize()

		lease.Reserve()
		lease.MACAddress = packet.CHAddr()

		if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
			lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
		}

		updated, err := this.LeasePool.UpdateLease(lease)
		if err != nil {
			return dhcp4.Packet{}, err
		}

		if !updated {
			//Unable to reserve lease (It's now active else where maybe?)
			return dhcp4.Packet{}, errors.New("Unable to Reserve Lease:" + lease.IP.String())
		}

		return offerPacket, nil
	case dhcp4.Request:
		//Request Received from client
		//Lets get the lease we're going to send them
		found, lease, err := this.GetLease(packet)
		if err != nil {
			return dhcp4.Packet{}, err
		}

		if !found {
			log.Println("It Looks Like Our Leases Are Depleted...")
			return dhcp4.Packet{}, nil
		}

		//If the lease is not the one requested We should send a NAK..
		if len(packetOptions) > 0 && !net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(lease.IP) {
			//NAK
			declinePacket := this.DeclinePacket(packet)
			declinePacket.PadToMinSize()

			return declinePacket, nil
		} else {
			lease.Active()
			lease.MACAddress = packet.CHAddr()

			lease.Expiry = time.Now().Add(this.Configuration.LeaseDuration)

			if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
				lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
			}

			updated, err := this.LeasePool.UpdateLease(lease)
			if err != nil {
				return dhcp4.Packet{}, err
			}

			if updated {
				//ACK
				acknowledgementPacket := this.AcknowledgementPacket(packet)
				acknowledgementPacket.SetYIAddr(lease.IP)

				//Lease time.
				acknowledgementPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(lease.Expiry.Sub(time.Now())))
				acknowledgementPacket.PadToMinSize()

				return acknowledgementPacket, nil
			} else {
				//NAK
				declinePacket := this.DeclinePacket(packet)
				declinePacket.PadToMinSize()

				return declinePacket, nil
			}
		}
	case dhcp4.Decline:
		//Decline from the client:
		log.Printf("Decline Message:%v\n", packet)

	case dhcp4.Release:
		//Decline from the client:
		log.Printf("Release Message:%v\n", packet)

	default:
		log.Printf("Unexpected Packet Type:%v\n", dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]))
	}

	return dhcp4.Packet{}, nil
}

/*
 * Create DHCP Offer Packet
 */
func (this *Server) OfferPacket(discoverPacket dhcp4.Packet) dhcp4.Packet {

	offerPacket := dhcp4.NewPacket(dhcp4.BootReply)
	offerPacket.SetXId(discoverPacket.XId())
	offerPacket.SetFlags(discoverPacket.Flags())

	offerPacket.SetCHAddr(discoverPacket.CHAddr())
	offerPacket.SetGIAddr(discoverPacket.GIAddr())
	offerPacket.SetSecs(discoverPacket.Secs())

	//53
	offerPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Offer)})
	//54
	offerPacket.AddOption(dhcp4.OptionServerIdentifier, this.Configuration.IP.To4())
	//51
	offerPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(this.Configuration.LeaseDuration))

	//Other options go in requested order...
	discoverPacketOptions := discoverPacket.ParseOptions()

	ourOptions := make(dhcp4.Options)

	//1
	ourOptions[dhcp4.OptionSubnetMask] = this.Configuration.SubnetMask.To4()
	//3
	ourOptions[dhcp4.OptionRouter] = this.Configuration.DefaultGateway.To4()
	//6
	ourOptions[dhcp4.OptionDomainNameServer] = dhcp4.JoinIPs(this.Configuration.DNSServers)

	if discoverPacketOptions[dhcp4.OptionParameterRequestList] != nil {
		//Loop through the requested options and if we have them add them.
		for _, optionCode := range discoverPacketOptions[dhcp4.OptionParameterRequestList] {
			if !bytes.Equal(ourOptions[dhcp4.OptionCode(optionCode)], []byte{}) {
				offerPacket.AddOption(dhcp4.OptionCode(optionCode), ourOptions[dhcp4.OptionCode(optionCode)])
				delete(ourOptions, dhcp4.OptionCode(optionCode))
			}
		}
	}

	//Add all the options not requested.
	for optionCode, optionValue := range ourOptions {
		offerPacket.AddOption(optionCode, optionValue)
	}

	return offerPacket

}

/*
 * Create DHCP Acknowledgement
 */
func (this *Server) AcknowledgementPacket(requestPacket dhcp4.Packet) dhcp4.Packet {

	acknowledgementPacket := dhcp4.NewPacket(dhcp4.BootReply)
	acknowledgementPacket.SetXId(requestPacket.XId())
	acknowledgementPacket.SetFlags(requestPacket.Flags())

	acknowledgementPacket.SetGIAddr(requestPacket.GIAddr())
	acknowledgementPacket.SetCHAddr(requestPacket.CHAddr())
	acknowledgementPacket.SetSecs(requestPacket.Secs())

	acknowledgementPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.ACK)})
	acknowledgementPacket.AddOption(dhcp4.OptionSubnetMask, this.Configuration.SubnetMask.To4())
	acknowledgementPacket.AddOption(dhcp4.OptionRouter, this.Configuration.DefaultGateway.To4())
	acknowledgementPacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(this.Configuration.DNSServers))
	acknowledgementPacket.AddOption(dhcp4.OptionServerIdentifier, this.Configuration.IP.To4())

	return acknowledgementPacket
}

/*
 * Create DHCP Decline
 */
func (this *Server) DeclinePacket(requestPacket dhcp4.Packet) dhcp4.Packet {

	declinePacket := dhcp4.NewPacket(dhcp4.BootReply)
	declinePacket.SetXId(requestPacket.XId())
	declinePacket.SetFlags(requestPacket.Flags())

	declinePacket.SetGIAddr(requestPacket.GIAddr())
	declinePacket.SetCHAddr(requestPacket.CHAddr())
	declinePacket.SetSecs(requestPacket.Secs())

	declinePacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.NAK)})
	declinePacket.AddOption(dhcp4.OptionSubnetMask, this.Configuration.SubnetMask.To4())
	declinePacket.AddOption(dhcp4.OptionRouter, this.Configuration.DefaultGateway.To4())
	declinePacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(this.Configuration.DNSServers))
	declinePacket.AddOption(dhcp4.OptionServerIdentifier, this.Configuration.IP.To4())

	return declinePacket
}

/*
 * Get Lease tries to work out the best lease for the packet supplied.
 * Taking into account all Requested IP, Exisitng MACAddresses and Free leases.
 */
func (t *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Lease, err error) {
	packetOptions := packet.ParseOptions()

	//Requested an IP
	if (len(packetOptions) > 0) &&
		packetOptions[dhcp4.OptionRequestedIPAddress] != nil &&
		!net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(net.IP{}) {
		//An IP Has Been Requested Let's Try and Get that One.

		found, lease, err = t.LeasePool.GetLease(net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]))
		if err != nil {
			return
		}

		if found {
			if lease.Status == leasepool.Free {
				//Lease Is Free you Can Have it.
				return
			}
			if lease.Status != leasepool.Free && bytes.Equal(lease.MACAddress, packet.CHAddr()) {
				//Lease isn't free but it's yours
				return
			}
		}
	}

	//Ok Even if you requested an IP you can't have it.
	found, lease, err = t.LeasePool.GetLeaseForHardwareAddress(packet.CHAddr())
	if found || err != nil {
		return
	}

	//Just get the next free lease if you can.
	found, lease, err = t.LeasePool.GetNextFreeLease()
	return
}

/*
 * Shutdown The Server Gracefully
 */
func (t *Server) Shutdown() {
	t.shutdown = true
}
<commit_msg>Working....<commit_after>package dhcp4server

import (
	"bytes"
	"errors"
	"github.com/d2g/dhcp4"
	"github.com/d2g/dhcp4server/leasepool"
	"log"
	"net"
	"time"
)

/*
 * The DHCP Server Structure
 */
type Server struct {
	Configuration *Configuration      //Server Configuration
	LeasePool     leasepool.LeasePool //Lease Pool Manager

	//Used to Gracefully Close the Server
	shutdown bool

	//Listeners & Response Connection.
	inbound *net.UDPConn
	outbound *net.UDPConn

}

/*
 * Start The DHCP Server
 */
func (this *Server) ListenAndServe() error {
	var err error

	inboundAddress := net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}

	outboundAddressFrom := net.UDPAddr{IP: this.Configuration.IP, Port: 67}
	outboundAddressTo := net.UDPAddr{IP: net.IPv4bcast, Port: 68}

	this.inbound, err = net.ListenUDP("udp4", &inboundAddress)
	if err != nil {
		return err
	}
	defer this.inbound.Close()

	//Make Our Buffer (Max Buffer is 574) "I believe this 576 size comes from RFC 791" - Random Mailing list quote of the day.
	buffer := make([]byte, 576)

	for {
	ListenForDHCPPackets:
		if this.shutdown {
			return nil
		}

		//Set Read Deadline
		this.inbound.SetReadDeadline(time.Now().Add(time.Second))
		// Read Packet
		n, source, err := this.inbound.ReadFrom(buffer)
		if err != nil {

			switch v := err.(type) {
			case *net.OpError:
				if v.Timeout() {
					goto ListenForDHCPPackets
				}
			case *net.AddrError:
				if v.Timeout() {
					goto ListenForDHCPPackets
				}
			case *net.UnknownNetworkError:
				if v.Timeout() {
					goto ListenForDHCPPackets
				}
			}

			log.Println("Error:" + err.Error())
			return err
		}

		//We should ignore some requests
		//It shouldn't be possible to ignore IP's because they shouldn't have them as we're the DHCP server.
		//However, they can have i.e. if you're the client & server :S.
		for _, ipToIgnore := range this.Configuration.IgnoreIPs {
			if ipToIgnore.Equal(source.(*net.UDPAddr).IP) {
				log.Println("Ignoring DHCP Request From IP:" + ipToIgnore.String())
				continue
			}
		}

		packet := dhcp4.Packet(buffer[:n])

		//We can ignore hardware addresses.
		//Usefull for ignoring a range of hardware addresses
		for _, hardwareAddressToIgnore := range this.Configuration.IgnoreHardwareAddress {
			if bytes.Equal(hardwareAddressToIgnore, packet.CHAddr()) {
				log.Println("Ignoring DHCP Request From Hardware Address:" + hardwareAddressToIgnore.String())
				continue
			}
		}

		log.Printf("Debug: Packet Received:%v\n", packet)
		log.Printf("Debug: Packet Received ID:%v\n", packet.XId())
		log.Printf("Debug: Packet Options:%v\n", packet.ParseOptions())
		log.Printf("Debug: Packet Client IP : %v\n", packet.CIAddr().String())
		log.Printf("Debug: Packet Your IP   : %v\n", packet.YIAddr().String())
		log.Printf("Debug: Packet Server IP : %v\n", packet.SIAddr().String())
		log.Printf("Debug: Packet Gateway IP: %v\n", packet.GIAddr().String())
		log.Printf("Debug: Packet Client Mac: %v\n", packet.CHAddr().String())

		//We need to stop butting in with other servers.
		if packet.SIAddr().Equal(net.IPv4(0, 0, 0, 0)) || packet.SIAddr().Equal(net.IP{}) || packet.SIAddr().Equal(this.Configuration.IP) {

			returnPacket, err := this.ServeDHCP(packet)
			if err != nil {
				log.Println("Error Serving DHCP:" + err.Error())
				return err
			}

			log.Printf("Debug: Packet Returned:%v\n", returnPacket)
			log.Printf("Debug: Packet Returned ID:%v\n", returnPacket.XId())
			log.Printf("Debug: Packet Options:%v\n", returnPacket.ParseOptions())
			log.Printf("Debug: Packet Client IP : %v\n", returnPacket.CIAddr().String())
			log.Printf("Debug: Packet Your IP   : %v\n", returnPacket.YIAddr().String())
			log.Printf("Debug: Packet Server IP : %v\n", returnPacket.SIAddr().String())
			log.Printf("Debug: Packet Gateway IP: %v\n", returnPacket.GIAddr().String())
			log.Printf("Debug: Packet Client Mac: %v\n", returnPacket.CHAddr().String())

			if len(packet) > 0 {
				this.inbound.Close()

				this.outbound, err = net.DialUDP("udp4", &outboundAddressFrom, &outboundAddressTo)
				if err != nil {
					return err
				}

				_, err := this.outbound.Write(returnPacket)
				this.outbound.Close()

				if err != nil {
					log.Println("Error Writing:" + err.Error())
					return err
				}

				this.inbound, err = net.ListenUDP("udp4", &inboundAddress)
				if err != nil {
					return err
				}
			}
		}

	}
}

func (this *Server) ServeDHCP(packet dhcp4.Packet) (dhcp4.Packet, error) {
	packetOptions := packet.ParseOptions()

	switch dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]) {
	case dhcp4.Discover:

		//Discover Received from client
		//Lets get the lease we're going to send them
		found, lease, err := this.GetLease(packet)
		if err != nil {
			return dhcp4.Packet{}, err
		}

		if !found {
			log.Println("It Looks Like Our Leases Are Depleted...")
			return dhcp4.Packet{}, nil
		}

		offerPacket := this.OfferPacket(packet)
		offerPacket.SetYIAddr(lease.IP)

		//Sort out the packet options
		log.Printf("%v\n", packetOptions[dhcp4.OptionParameterRequestList])

		offerPacket.PadToMinSize()

		lease.Reserve()
		lease.MACAddress = packet.CHAddr()

		if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
			lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
		}

		updated, err := this.LeasePool.UpdateLease(lease)
		if err != nil {
			return dhcp4.Packet{}, err
		}

		if !updated {
			//Unable to reserve lease (It's now active else where maybe?)
			return dhcp4.Packet{}, errors.New("Unable to Reserve Lease:" + lease.IP.String())
		}

		return offerPacket, nil
	case dhcp4.Request:
		//Request Received from client
		//Lets get the lease we're going to send them
		found, lease, err := this.GetLease(packet)
		if err != nil {
			return dhcp4.Packet{}, err
		}

		if !found {
			log.Println("It Looks Like Our Leases Are Depleted...")
			return dhcp4.Packet{}, nil
		}

		//If the lease is not the one requested We should send a NAK..
		if len(packetOptions) > 0 && !net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(lease.IP) {
			//NAK
			declinePacket := this.DeclinePacket(packet)
			declinePacket.PadToMinSize()

			return declinePacket, nil
		} else {
			lease.Active()
			lease.MACAddress = packet.CHAddr()

			lease.Expiry = time.Now().Add(this.Configuration.LeaseDuration)

			if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
				lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
			}

			updated, err := this.LeasePool.UpdateLease(lease)
			if err != nil {
				return dhcp4.Packet{}, err
			}

			if updated {
				//ACK
				acknowledgementPacket := this.AcknowledgementPacket(packet)
				acknowledgementPacket.SetYIAddr(lease.IP)

				//Lease time.
				acknowledgementPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(lease.Expiry.Sub(time.Now())))
				acknowledgementPacket.PadToMinSize()

				return acknowledgementPacket, nil
			} else {
				//NAK
				declinePacket := this.DeclinePacket(packet)
				declinePacket.PadToMinSize()

				return declinePacket, nil
			}
		}
	case dhcp4.Decline:
		//Decline from the client:
		log.Printf("Decline Message:%v\n", packet)

	case dhcp4.Release:
		//Decline from the client:
		log.Printf("Release Message:%v\n", packet)

	default:
		log.Printf("Unexpected Packet Type:%v\n", dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]))
	}

	return dhcp4.Packet{}, nil
}

/*
 * Create DHCP Offer Packet
 */
func (this *Server) OfferPacket(discoverPacket dhcp4.Packet) dhcp4.Packet {

	offerPacket := dhcp4.NewPacket(dhcp4.BootReply)
	offerPacket.SetXId(discoverPacket.XId())
	offerPacket.SetFlags(discoverPacket.Flags())

	offerPacket.SetCHAddr(discoverPacket.CHAddr())
	offerPacket.SetGIAddr(discoverPacket.GIAddr())
	offerPacket.SetSecs(discoverPacket.Secs())

	//53
	offerPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Offer)})
	//54
	offerPacket.AddOption(dhcp4.OptionServerIdentifier, this.Configuration.IP.To4())
	//51
	offerPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(this.Configuration.LeaseDuration))

	//Other options go in requested order...
	discoverPacketOptions := discoverPacket.ParseOptions()

	ourOptions := make(dhcp4.Options)

	//1
	ourOptions[dhcp4.OptionSubnetMask] = this.Configuration.SubnetMask.To4()
	//3
	ourOptions[dhcp4.OptionRouter] = this.Configuration.DefaultGateway.To4()
	//6
	ourOptions[dhcp4.OptionDomainNameServer] = dhcp4.JoinIPs(this.Configuration.DNSServers)

	if discoverPacketOptions[dhcp4.OptionParameterRequestList] != nil {
		//Loop through the requested options and if we have them add them.
		for _, optionCode := range discoverPacketOptions[dhcp4.OptionParameterRequestList] {
			if !bytes.Equal(ourOptions[dhcp4.OptionCode(optionCode)], []byte{}) {
				offerPacket.AddOption(dhcp4.OptionCode(optionCode), ourOptions[dhcp4.OptionCode(optionCode)])
				delete(ourOptions, dhcp4.OptionCode(optionCode))
			}
		}
	}

	//Add all the options not requested.
	for optionCode, optionValue := range ourOptions {
		offerPacket.AddOption(optionCode, optionValue)
	}

	return offerPacket

}

/*
 * Create DHCP Acknowledgement
 */
func (this *Server) AcknowledgementPacket(requestPacket dhcp4.Packet) dhcp4.Packet {

	acknowledgementPacket := dhcp4.NewPacket(dhcp4.BootReply)
	acknowledgementPacket.SetXId(requestPacket.XId())
	acknowledgementPacket.SetFlags(requestPacket.Flags())

	acknowledgementPacket.SetGIAddr(requestPacket.GIAddr())
	acknowledgementPacket.SetCHAddr(requestPacket.CHAddr())
	acknowledgementPacket.SetSecs(requestPacket.Secs())

	acknowledgementPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.ACK)})
	acknowledgementPacket.AddOption(dhcp4.OptionSubnetMask, this.Configuration.SubnetMask.To4())
	acknowledgementPacket.AddOption(dhcp4.OptionRouter, this.Configuration.DefaultGateway.To4())
	acknowledgementPacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(this.Configuration.DNSServers))
	acknowledgementPacket.AddOption(dhcp4.OptionServerIdentifier, this.Configuration.IP.To4())

	return acknowledgementPacket
}

/*
 * Create DHCP Decline
 */
func (this *Server) DeclinePacket(requestPacket dhcp4.Packet) dhcp4.Packet {

	declinePacket := dhcp4.NewPacket(dhcp4.BootReply)
	declinePacket.SetXId(requestPacket.XId())
	declinePacket.SetFlags(requestPacket.Flags())

	declinePacket.SetGIAddr(requestPacket.GIAddr())
	declinePacket.SetCHAddr(requestPacket.CHAddr())
	declinePacket.SetSecs(requestPacket.Secs())

	declinePacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.NAK)})
	declinePacket.AddOption(dhcp4.OptionSubnetMask, this.Configuration.SubnetMask.To4())
	declinePacket.AddOption(dhcp4.OptionRouter, this.Configuration.DefaultGateway.To4())
	declinePacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(this.Configuration.DNSServers))
	declinePacket.AddOption(dhcp4.OptionServerIdentifier, this.Configuration.IP.To4())

	return declinePacket
}

/*
 * Get Lease tries to work out the best lease for the packet supplied.
 * Taking into account all Requested IP, Exisitng MACAddresses and Free leases.
 */
func (t *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Lease, err error) {
	packetOptions := packet.ParseOptions()

	//Requested an IP
	if (len(packetOptions) > 0) &&
		packetOptions[dhcp4.OptionRequestedIPAddress] != nil &&
		!net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(net.IP{}) {
		//An IP Has Been Requested Let's Try and Get that One.

		found, lease, err = t.LeasePool.GetLease(net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]))
		if err != nil {
			return
		}

		if found {
			if lease.Status == leasepool.Free {
				//Lease Is Free you Can Have it.
				return
			}
			if lease.Status != leasepool.Free && bytes.Equal(lease.MACAddress, packet.CHAddr()) {
				//Lease isn't free but it's yours
				return
			}
		}
	}

	//Ok Even if you requested an IP you can't have it.
	found, lease, err = t.LeasePool.GetLeaseForHardwareAddress(packet.CHAddr())
	if found || err != nil {
		return
	}

	//Just get the next free lease if you can.
	found, lease, err = t.LeasePool.GetNextFreeLease()
	return
}

/*
 * Shutdown The Server Gracefully
 */
func (t *Server) Shutdown() {
	t.shutdown = true
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha1"
	"fmt"
	"io"
	"log"
	"net"
	"regexp"
	"strings"
)

var (
	nickRegexp    = regexp.MustCompile(`^[a-zA-Z\[\]_^{|}][a-zA-Z0-9\[\]_^{|}]*$`)
	channelRegexp = regexp.MustCompile(`^#[a-zA-Z0-9_\-]+$`)
)

func NewServer() *Server {
	return &Server{eventChan: make(chan Event),
		name:        "rosella",
		clientMap:   make(map[string]*Client),
		channelMap:  make(map[string]*Channel),
		operatorMap: make(map[string]string)}
}

func (s *Server) Run() {
	for event := range s.eventChan {
		s.handleEvent(event)
	}
}

func (s *Server) HandleConnection(conn net.Conn) {
	client := &Client{server: s,
		connection: conn,
		outputChan: make(chan string),
		signalChan: make(chan signalCode, 3),
		channelMap: make(map[string]*Channel),
		connected:  true}

	go client.clientThread()
}

func (s *Server) handleEvent(e Event) {
	defer func(event Event) {
		err := recover()
		if err != nil {
			log.Printf("Recovered from errer when handling event: %+v", event)
			log.Println(err)
		}
	}(e)
	fields := strings.Fields(e.input)

	if len(fields) < 1 {
		return
	}

	if strings.HasPrefix(fields[0], ":") {
		fields = fields[1:]
	}

	command := strings.ToUpper(fields[0])
	args := fields[1:]

	switch {
	case command == "INFO":
		e.client.reply(rplInfo, "Rosella IRCD github.com/eXeC64/Rosella")
	case command == "NICK":
		if len(args) < 1 {
			e.client.reply(errNoNick)
			return
		}

		newNick := args[0]

		//Check newNick is of valid formatting (regex)
		if nickRegexp.MatchString(newNick) == false {
			e.client.reply(errInvalidNick, newNick)
			return
		}

		if _, exists := s.clientMap[strings.ToLower(newNick)]; exists {
			e.client.reply(errNickInUse, newNick)
			return
		}

		//Protect the server name from being used
		if strings.ToLower(newNick) == strings.ToLower(s.name) {
			e.client.reply(errNickInUse, newNick)
			return
		}

		e.client.setNick(newNick)

	case command == "USER":
		if e.client.nick == "" {
			e.client.reply(rplKill, "Your nickname is already being used")
			e.client.disconnect()
		} else {
			e.client.reply(rplWelcome)
			e.client.registered = true
		}

	case command == "JOIN":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		if args[0] == "0" {
			//Quit all channels
			for channel := range e.client.channelMap {
				e.client.partChannel(channel, "Disconnecting")
			}
			return
		}

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Join the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				e.client.joinChannel(channel)
			}
		}

	case command == "PART":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		reason := strings.Join(args[1:], " ")

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Part the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				e.client.partChannel(channel, reason)
			}
		}

	case command == "PRIVMSG":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		message := strings.Join(args[1:], " ")

		channel, chanExists := s.channelMap[strings.ToLower(args[0])]
		client, clientExists := s.clientMap[strings.ToLower(args[0])]

		if chanExists {
			if channel.mode.noExternal {
				if _, inChannel := channel.clientMap[strings.ToLower(e.client.nick)]; !inChannel {
					//Not in channel, not allowed to send
					e.client.reply(errCannotSend, args[0])
					return
				}
			}
			if channel.mode.moderated {
				clientMode := channel.modeMap[strings.ToLower(e.client.nick)]
				if !clientMode.operator && !clientMode.voice {
					//It's moderated and we're not +v or +o, do nothing
					e.client.reply(errCannotSend, args[0])
					return
				}
			}
			for _, c := range channel.clientMap {
				if c != e.client {
					c.reply(rplMsg, e.client.nick, args[0], message)
				}
			}
		} else if clientExists {
			client.reply(rplMsg, e.client.nick, client.nick, message)
		} else {
			e.client.reply(errNoSuchNick, args[0])
		}

	case command == "QUIT":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		e.client.disconnect()

	case command == "TOPIC":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channel, exists := s.channelMap[strings.ToLower(args[0])]
		if exists == false {
			e.client.reply(errNoSuchNick, args[0])
			return
		}

		if len(args) == 1 {
			e.client.reply(rplTopic, channel.name, channel.topic)
			return
		}

		clientMode := channel.modeMap[strings.ToLower(e.client.nick)]
		if channel.mode.topicLocked && !clientMode.operator {
			e.client.reply(errNoPriv)
			return
		}

		if args[1] == ":" {
			channel.topic = ""
			for _, client := range channel.clientMap {
				client.reply(rplNoTopic, channel.name)
			}
		} else {
			topic := strings.Join(args[1:], " ")
			topic = strings.TrimPrefix(topic, ":")
			channel.topic = topic

			for _, client := range channel.clientMap {
				client.reply(rplTopic, channel.name, channel.topic)
			}
		}

	case command == "LIST":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) == 0 {
			chanList := make([]string, 0, len(s.channelMap))

			for channelName, channel := range s.channelMap {
				if channel.mode.secret {
					if _, inChannel := channel.clientMap[strings.ToLower(e.client.nick)]; !inChannel {
						//Not in the channel, skip
						continue
					}
				}
				listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
				chanList = append(chanList, listItem)
			}

			e.client.reply(rplList, chanList...)

		} else {
			channels := strings.Split(args[0], ",")
			chanList := make([]string, 0, len(channels))

			for _, channelName := range channels {
				if channel, exists := s.channelMap[strings.ToLower(channelName)]; exists {
					listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
					chanList = append(chanList, listItem)
				}
			}

			e.client.reply(rplList, chanList...)
		}
	case command == "OPER":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		username := args[0]
		password := args[1]

		if hashedPassword, exists := s.operatorMap[username]; exists {
			h := sha1.New()
			io.WriteString(h, password)
			pass := fmt.Sprintf("%x", h.Sum(nil))
			if hashedPassword == pass {
				e.client.operator = true
				e.client.reply(rplOper)
				return
			}
		}
		e.client.reply(errPassword)

	case command == "KILL":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if e.client.operator == false {
			e.client.reply(errNoPriv)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		nick := args[0]

		reason := strings.Join(args[1:], " ")

		if client, exists := s.clientMap[strings.ToLower(nick)]; exists {
			client.reply(rplKill, e.client.nick, reason)
			client.disconnect()
		} else {
			e.client.reply(errNoSuchNick, nick)
		}

	case command == "KICK":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		channelKey := strings.ToLower(args[0])
		targetKey := strings.ToLower(args[1])

		channel, channelExists := s.channelMap[channelKey]
		if !channelExists {
			e.client.reply(errNoSuchNick, args[0])
			return
		}

		target, targetExists := channel.clientMap[targetKey]
		if !targetExists {
			e.client.reply(errNoSuchNick, args[1])
			return
		}

		clientMode := channel.modeMap[e.client.key]
		if !clientMode.operator && !e.client.operator {
			e.client.reply(errNoPriv)
			return
		}

		reason := strings.Join(args[2:], " ")

		//It worked
		for _, client := range channel.clientMap {
			client.reply(rplKick, e.client.nick, channel.name, target.nick, reason)
		}

		delete(channel.clientMap, targetKey)
		delete(channel.modeMap, targetKey)
		delete(target.channelMap, channelKey)

	case command == "MODE":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channelKey := strings.ToLower(args[0])

		channel, channelExists := s.channelMap[channelKey]
		if !channelExists {
			e.client.reply(errNoSuchNick, args[0])
			return
		}
		mode := channel.mode

		if len(args) == 1 {
			//No more args, they just want the mode
			e.client.reply(rplChannelModeIs, args[0], mode.String(), "")
			return
		}

		if cm, ok := channel.modeMap[strings.ToLower(e.client.nick)]; !ok || !cm.operator {
			//Not a channel operator.

			//If they're not an irc operator either, they'll fail
			if !e.client.operator {
				e.client.reply(errNoPriv)
				return
			}
		}

		hasClient := false
		var oldClientMode, newClientMode *ClientMode
		var targetClient *Client
		if len(args) >= 3 {
			clientKey := strings.ToLower(args[2])
			oldClientMode, hasClient = channel.modeMap[clientKey]
			if hasClient {
				targetClient = channel.clientMap[clientKey]
				newClientMode = new(ClientMode)
				*newClientMode = *oldClientMode
			}
		}

		mod := strings.ToLower(args[1])
		if strings.HasPrefix(mod, "+") {
			for _, char := range mod {
				switch char {
				case 's':
					mode.secret = true
				case 't':
					mode.topicLocked = true
				case 'm':
					mode.moderated = true
				case 'n':
					mode.noExternal = true
				case 'o':
					if hasClient {
						newClientMode.operator = true
					}
				case 'v':
					if hasClient {
						newClientMode.voice = true
					}
				}
			}
		} else if strings.HasPrefix(mod, "-") {
			for _, char := range mod {
				switch char {
				case 's':
					mode.secret = false
				case 't':
					mode.topicLocked = false
				case 'm':
					mode.moderated = false
				case 'n':
					mode.noExternal = false
				case 'o':
					if hasClient {
						newClientMode.operator = false
					}
				case 'v':
					if hasClient {
						newClientMode.voice = false
					}
				}
			}
		}

		if hasClient {
			*oldClientMode = *newClientMode
		}
		channel.mode = mode

		for _, client := range channel.clientMap {
			if hasClient {
				client.reply(rplChannelModeIs, args[0], args[1], targetClient.nick)
			} else {
				client.reply(rplChannelModeIs, args[0], args[1], "")
			}
		}

	default:
		e.client.reply(errUnknownCommand, command)
	}
}
<commit_msg>Small tidy up in Server.handleEvent()<commit_after>package main

import (
	"crypto/sha1"
	"fmt"
	"io"
	"log"
	"net"
	"regexp"
	"strings"
)

var (
	nickRegexp    = regexp.MustCompile(`^[a-zA-Z\[\]_^{|}][a-zA-Z0-9\[\]_^{|}]*$`)
	channelRegexp = regexp.MustCompile(`^#[a-zA-Z0-9_\-]+$`)
)

func NewServer() *Server {
	return &Server{eventChan: make(chan Event),
		name:        "rosella",
		clientMap:   make(map[string]*Client),
		channelMap:  make(map[string]*Channel),
		operatorMap: make(map[string]string)}
}

func (s *Server) Run() {
	for event := range s.eventChan {
		s.handleEvent(event)
	}
}

func (s *Server) HandleConnection(conn net.Conn) {
	client := &Client{server: s,
		connection: conn,
		outputChan: make(chan string),
		signalChan: make(chan signalCode, 3),
		channelMap: make(map[string]*Channel),
		connected:  true}

	go client.clientThread()
}

func (s *Server) handleEvent(e Event) {
	defer func(event Event) {
		err := recover()
		if err != nil {
			log.Printf("Recovered from errer when handling event: %+v", event)
			log.Println(err)
		}
	}(e)
	fields := strings.Fields(e.input)

	if len(fields) < 1 {
		return
	}

	if strings.HasPrefix(fields[0], ":") {
		fields = fields[1:]
	}

	command := strings.ToUpper(fields[0])
	args := fields[1:]

	switch {
	case command == "INFO":
		e.client.reply(rplInfo, "Rosella IRCD github.com/eXeC64/Rosella")
	case command == "NICK":
		if len(args) < 1 {
			e.client.reply(errNoNick)
			return
		}

		newNick := args[0]

		//Check newNick is of valid formatting (regex)
		if nickRegexp.MatchString(newNick) == false {
			e.client.reply(errInvalidNick, newNick)
			return
		}

		if _, exists := s.clientMap[strings.ToLower(newNick)]; exists {
			e.client.reply(errNickInUse, newNick)
			return
		}

		//Protect the server name from being used
		if strings.ToLower(newNick) == strings.ToLower(s.name) {
			e.client.reply(errNickInUse, newNick)
			return
		}

		e.client.setNick(newNick)

	case command == "USER":
		if e.client.nick == "" {
			e.client.reply(rplKill, "Your nickname is already being used")
			e.client.disconnect()
		} else {
			e.client.reply(rplWelcome)
			e.client.registered = true
		}

	case command == "JOIN":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		if args[0] == "0" {
			//Quit all channels
			for channel := range e.client.channelMap {
				e.client.partChannel(channel, "Disconnecting")
			}
			return
		}

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Join the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				e.client.joinChannel(channel)
			}
		}

	case command == "PART":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		reason := strings.Join(args[1:], " ")

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Part the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				e.client.partChannel(channel, reason)
			}
		}

	case command == "PRIVMSG":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		message := strings.Join(args[1:], " ")

		channel, chanExists := s.channelMap[strings.ToLower(args[0])]
		client, clientExists := s.clientMap[strings.ToLower(args[0])]

		if chanExists {
			if channel.mode.noExternal {
				if _, inChannel := channel.clientMap[strings.ToLower(e.client.nick)]; !inChannel {
					//Not in channel, not allowed to send
					e.client.reply(errCannotSend, args[0])
					return
				}
			}
			if channel.mode.moderated {
				clientMode := channel.modeMap[strings.ToLower(e.client.nick)]
				if !clientMode.operator && !clientMode.voice {
					//It's moderated and we're not +v or +o, do nothing
					e.client.reply(errCannotSend, args[0])
					return
				}
			}
			for _, c := range channel.clientMap {
				if c != e.client {
					c.reply(rplMsg, e.client.nick, args[0], message)
				}
			}
		} else if clientExists {
			client.reply(rplMsg, e.client.nick, client.nick, message)
		} else {
			e.client.reply(errNoSuchNick, args[0])
		}

	case command == "QUIT":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		e.client.disconnect()

	case command == "TOPIC":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channel, exists := s.channelMap[strings.ToLower(args[0])]
		if exists == false {
			e.client.reply(errNoSuchNick, args[0])
			return
		}

		if len(args) == 1 {
			e.client.reply(rplTopic, channel.name, channel.topic)
			return
		}

		clientMode := channel.modeMap[strings.ToLower(e.client.nick)]
		if channel.mode.topicLocked && !clientMode.operator {
			e.client.reply(errNoPriv)
			return
		}

		if args[1] == ":" {
			channel.topic = ""
			for _, client := range channel.clientMap {
				client.reply(rplNoTopic, channel.name)
			}
		} else {
			topic := strings.Join(args[1:], " ")
			topic = strings.TrimPrefix(topic, ":")
			channel.topic = topic

			for _, client := range channel.clientMap {
				client.reply(rplTopic, channel.name, channel.topic)
			}
		}

	case command == "LIST":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) == 0 {
			chanList := make([]string, 0, len(s.channelMap))

			for channelName, channel := range s.channelMap {
				if channel.mode.secret {
					if _, inChannel := channel.clientMap[strings.ToLower(e.client.nick)]; !inChannel {
						//Not in the channel, skip
						continue
					}
				}
				listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
				chanList = append(chanList, listItem)
			}

			e.client.reply(rplList, chanList...)

		} else {
			channels := strings.Split(args[0], ",")
			chanList := make([]string, 0, len(channels))

			for _, channelName := range channels {
				if channel, exists := s.channelMap[strings.ToLower(channelName)]; exists {
					listItem := fmt.Sprintf("%s %d :%s", channelName, len(channel.clientMap), channel.topic)
					chanList = append(chanList, listItem)
				}
			}

			e.client.reply(rplList, chanList...)
		}
	case command == "OPER":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		username := args[0]
		password := args[1]

		if hashedPassword, exists := s.operatorMap[username]; exists {
			h := sha1.New()
			io.WriteString(h, password)
			pass := fmt.Sprintf("%x", h.Sum(nil))
			if hashedPassword == pass {
				e.client.operator = true
				e.client.reply(rplOper)
				return
			}
		}
		e.client.reply(errPassword)

	case command == "KILL":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if e.client.operator == false {
			e.client.reply(errNoPriv)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		nick := args[0]

		reason := strings.Join(args[1:], " ")

		client, exists := s.clientMap[strings.ToLower(nick)]
		if !exists {
			e.client.reply(errNoSuchNick, nick)
			return
		}

		client.reply(rplKill, e.client.nick, reason)
		client.disconnect()

	case command == "KICK":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		channelKey := strings.ToLower(args[0])
		targetKey := strings.ToLower(args[1])

		channel, channelExists := s.channelMap[channelKey]
		if !channelExists {
			e.client.reply(errNoSuchNick, args[0])
			return
		}

		target, targetExists := channel.clientMap[targetKey]
		if !targetExists {
			e.client.reply(errNoSuchNick, args[1])
			return
		}

		clientMode := channel.modeMap[e.client.key]
		if !clientMode.operator && !e.client.operator {
			e.client.reply(errNoPriv)
			return
		}

		reason := strings.Join(args[2:], " ")

		//It worked
		for _, client := range channel.clientMap {
			client.reply(rplKick, e.client.nick, channel.name, target.nick, reason)
		}

		delete(channel.clientMap, targetKey)
		delete(channel.modeMap, targetKey)
		delete(target.channelMap, channelKey)

	case command == "MODE":
		if e.client.registered == false {
			e.client.reply(errNotReg)
			return
		}

		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channelKey := strings.ToLower(args[0])

		channel, channelExists := s.channelMap[channelKey]
		if !channelExists {
			e.client.reply(errNoSuchNick, args[0])
			return
		}
		mode := channel.mode

		if len(args) == 1 {
			//No more args, they just want the mode
			e.client.reply(rplChannelModeIs, args[0], mode.String(), "")
			return
		}

		if cm, ok := channel.modeMap[strings.ToLower(e.client.nick)]; !ok || !cm.operator {
			//Not a channel operator.

			//If they're not an irc operator either, they'll fail
			if !e.client.operator {
				e.client.reply(errNoPriv)
				return
			}
		}

		hasClient := false
		var oldClientMode, newClientMode *ClientMode
		var targetClient *Client
		if len(args) >= 3 {
			clientKey := strings.ToLower(args[2])
			oldClientMode, hasClient = channel.modeMap[clientKey]
			if hasClient {
				targetClient = channel.clientMap[clientKey]
				newClientMode = new(ClientMode)
				*newClientMode = *oldClientMode
			}
		}

		mod := strings.ToLower(args[1])
		if strings.HasPrefix(mod, "+") {
			for _, char := range mod {
				switch char {
				case 's':
					mode.secret = true
				case 't':
					mode.topicLocked = true
				case 'm':
					mode.moderated = true
				case 'n':
					mode.noExternal = true
				case 'o':
					if hasClient {
						newClientMode.operator = true
					}
				case 'v':
					if hasClient {
						newClientMode.voice = true
					}
				}
			}
		} else if strings.HasPrefix(mod, "-") {
			for _, char := range mod {
				switch char {
				case 's':
					mode.secret = false
				case 't':
					mode.topicLocked = false
				case 'm':
					mode.moderated = false
				case 'n':
					mode.noExternal = false
				case 'o':
					if hasClient {
						newClientMode.operator = false
					}
				case 'v':
					if hasClient {
						newClientMode.voice = false
					}
				}
			}
		}

		if hasClient {
			*oldClientMode = *newClientMode
		}
		channel.mode = mode

		for _, client := range channel.clientMap {
			if hasClient {
				client.reply(rplChannelModeIs, args[0], args[1], targetClient.nick)
			} else {
				client.reply(rplChannelModeIs, args[0], args[1], "")
			}
		}

	default:
		e.client.reply(errUnknownCommand, command)
	}
}
<|endoftext|>


<commit_before>package http

import (
	"net/http"

	"github.com/gorilla/pat"
	"github.com/ian-kent/go-log/log"
)

// Listen binds to httpBindAddr
func Listen(httpBindAddr string, Asset func(string) ([]byte, error), exitCh chan int, registerCallback func(http.Handler)) {
	log.Info("[HTTP] Binding to address: %s", httpBindAddr)

	pat := pat.New()
	registerCallback(pat)

	err := http.ListenAndServe(httpBindAddr, pat)
	if err != nil {
		log.Fatalf("[HTTP] Error binding to address %s: %s", httpBindAddr, err)
	}
}
<commit_msg>Add HTTP authentication for mailhog/MailHog#40<commit_after>package http

import (
	"bytes"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"

	"github.com/gorilla/pat"
	"github.com/ian-kent/go-log/log"
	"golang.org/x/crypto/bcrypt"
)

// Authorised should be given a function to enable HTTP Basic Authentication
var Authorised func(string, string) bool
var users map[string]string

// AuthFile sets Authorised to a function which validates against file
func AuthFile(file string) {
	users = make(map[string]string)

	b, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatalf("[HTTP] Error reading auth-file: %s", err)
		// FIXME - go-log
		os.Exit(1)
	}

	buf := bytes.NewBuffer(b)

	for {
		l, err := buf.ReadString('\n')
		l = strings.TrimSpace(l)
		if len(l) > 0 {
			p := strings.SplitN(l, ":", 2)
			if len(p) < 2 {
				log.Fatalf("[HTTP] Error reading auth-file, invalid line: %s", l)
				// FIXME - go-log
				os.Exit(1)
			}
			users[p[0]] = p[1]
		}
		switch {
		case err == io.EOF:
			break
		case err != nil:
			log.Fatalf("[HTTP] Error reading auth-file: %s", err)
			// FIXME - go-log
			os.Exit(1)
			break
		}
		if err == io.EOF {
			break
		} else if err != nil {
		}
	}

	log.Printf("[HTTP] Loaded %d users from %s", len(users), file)

	Authorised = func(u, pw string) bool {
		hpw, ok := users[u]
		if !ok {
			return false
		}

		err := bcrypt.CompareHashAndPassword([]byte(hpw), []byte(pw))
		if err != nil {
			return false
		}

		return true
	}
}

// Listen binds to httpBindAddr
func Listen(httpBindAddr string, Asset func(string) ([]byte, error), exitCh chan int, registerCallback func(http.Handler)) {
	log.Info("[HTTP] Binding to address: %s", httpBindAddr)

	pat := pat.New()
	registerCallback(pat)

	f := func(w http.ResponseWriter, req *http.Request) {
		if Authorised == nil {
			pat.ServeHTTP(w, req)
			return
		}

		u, pw, ok := req.BasicAuth()
		if !ok || !Authorised(u, pw) {
			w.Header().Set("WWW-Authenticate", "Basic")
			w.WriteHeader(401)
			return
		}
		pat.ServeHTTP(w, req)
	}

	err := http.ListenAndServe(httpBindAddr, http.HandlerFunc(f))
	if err != nil {
		log.Fatalf("[HTTP] Error binding to address %s: %s", httpBindAddr, err)
	}
}
<|endoftext|>


<commit_before>package main

import (
  "fmt"
  "log"
  "net/http"
  "strings"
  "time"
  "encoding/json"

  "github.com/marcelocajueiro/url_shortener/urls"
)

var (
  port    int
  urlBase string
)

func init() {
  port    = 8888
  urlBase = fmt.Sprintf("http://localhost:%d", port)
}

func main() {
  stats := make (chan string)
  defer close (stats)
  go newStatistic(stats)

  http.HandleFunc("/api/shorten", Shortener)
  http.Handle("/r/", &Redirector{stats})
  http.HandleFunc("/api/stats/", StatsViewer)

  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

type Redirector struct {
  stats chan string
}
type Headers map[string]string
type Url struct {
  Id        string
  CreatedAt time.Time
  Destiny   string
}

func Shortener(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    respondWith(w, http.StatusMethodNotAllowed, Headers{
      "Allow": "POST",
    })
    return
  }

  url, new, err := urls.FindOrCreateNewUrl(extractUrl(r))

  if err != nil {
    respondWith(w, http.StatusBadRequest, nil)
    return
  }

  var status int

  if new {
    status = http.StatusCreated
  } else {
    status = http.StatusOK
  }

  shortUrl := fmt.Sprintf("%s/r/%s", urlBase, url.Id)
  respondWith(w, status, Headers{
    "Location": shortUrl,
    "Link": fmt.Sprintf("<%s/api/stats/%s>; rel=\"stats\"", urlBase, url.Id),
  })
}

func (red *Redirector) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  findUrlAndExecute(w, r, func (url *urls.Url) {
    http.Redirect(w, r, url.Destiny, http.StatusMovedPermanently)
    red.stats <- url.Id
  })
}

func StatsViewer(w http.ResponseWriter, r *http.Request) {
  findUrlAndExecute(w, r, func (url *urls.Url) {
    json, err := json.Marshal(url.Stats())

    if err != nil {
      w.WriteHeader(http.StatusInternalServerError)
      return
    }

    respondWithJSON(w, string(json))
  })
}

func findUrlAndExecute(
  w http.ResponseWriter,
  r *http.Request,
  executor func(*urls.Url),
) {
  path := strings.Split(r.URL.Path, "/")
  id := path[len(path) -1]

  if url := urls.Search(id); url != nil {
    executor(url)
  } else {
    http.NotFound(w, r)
  }
}

func respondWith(w http.ResponseWriter, status int, headers Headers) {
  for k, v := range headers {
    w.Header().Set(k, v)
  }
  w.WriteHeader(status)
}

func respondWithJSON(w http.ResponseWriter, response string) {
  respondWith(w, http.StatusOK, Headers{
    "Content-Type": "application/json",
  })
  fmt.Fprintf(w, response)
}

func extractUrl(r *http.Request) string {
  url := make([]byte, r.ContentLength, r.ContentLength)
  r.Body.Read(url)
  return string (url)
}

func newStatistic(ids <-chan string) {
  for id := range ids {
    urls.RegisterClick(id)
    fmt.Printf("Click in %s.\n", id)
  }
}
<commit_msg>Add some log to the server<commit_after>package main

import (
  "fmt"
  "log"
  "net/http"
  "strings"
  "time"
  "encoding/json"

  "github.com/marcelocajueiro/url_shortener/urls"
)

var (
  port    int
  urlBase string
)

func init() {
  port    = 8888
  urlBase = fmt.Sprintf("http://localhost:%d", port)
}

func main() {
  stats := make (chan string)
  defer close (stats)
  go newStatistic(stats)

  http.HandleFunc("/api/shorten", Shortener)
  http.Handle("/r/", &Redirector{stats})
  http.HandleFunc("/api/stats/", StatsViewer)

  printLog("Starting server on port %d...", port)
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

type Redirector struct {
  stats chan string
}
type Headers map[string]string
type Url struct {
  Id        string
  CreatedAt time.Time
  Destiny   string
}

func Shortener(w http.ResponseWriter, r *http.Request) {
  if r.Method != "POST" {
    respondWith(w, http.StatusMethodNotAllowed, Headers{
      "Allow": "POST",
    })
    return
  }

  url, new, err := urls.FindOrCreateNewUrl(extractUrl(r))

  if err != nil {
    respondWith(w, http.StatusBadRequest, nil)
    return
  }

  var status int

  if new {
    status = http.StatusCreated
  } else {
    status = http.StatusOK
  }

  shortUrl := fmt.Sprintf("%s/r/%s", urlBase, url.Id)
  respondWith(w, status, Headers{
    "Location": shortUrl,
    "Link": fmt.Sprintf("<%s/api/stats/%s>; rel=\"stats\"", urlBase, url.Id),
  })

  printLog("URL %s successfully shortened to %s", url.Destiny, shortUrl)
}

func (red *Redirector) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  findUrlAndExecute(w, r, func (url *urls.Url) {
    http.Redirect(w, r, url.Destiny, http.StatusMovedPermanently)
    red.stats <- url.Id
  })
}

func StatsViewer(w http.ResponseWriter, r *http.Request) {
  findUrlAndExecute(w, r, func (url *urls.Url) {
    json, err := json.Marshal(url.Stats())

    if err != nil {
      w.WriteHeader(http.StatusInternalServerError)
      return
    }

    respondWithJSON(w, string(json))
  })
}

func findUrlAndExecute(
  w http.ResponseWriter,
  r *http.Request,
  executor func(*urls.Url),
) {
  path := strings.Split(r.URL.Path, "/")
  id := path[len(path) -1]

  if url := urls.Search(id); url != nil {
    executor(url)
  } else {
    http.NotFound(w, r)
  }
}

func respondWith(w http.ResponseWriter, status int, headers Headers) {
  for k, v := range headers {
    w.Header().Set(k, v)
  }
  w.WriteHeader(status)
}

func respondWithJSON(w http.ResponseWriter, response string) {
  respondWith(w, http.StatusOK, Headers{
    "Content-Type": "application/json",
  })
  fmt.Fprintf(w, response)
}

func extractUrl(r *http.Request) string {
  url := make([]byte, r.ContentLength, r.ContentLength)
  r.Body.Read(url)
  return string (url)
}

func newStatistic(ids <-chan string) {
  for id := range ids {
    urls.RegisterClick(id)
    printLog("%s was clicked", id)
  }
}

func printLog(format string, values ...interface{}) {
  // log.Printf("Testando %d %d %d", 1, 2, 3)
  log.Printf(fmt.Sprintf("%s\n", format), values...)
}
<|endoftext|>