skateman/purr

View on GitHub
client/server.go

Summary

Maintainability
A
2 hrs
Test Coverage
package main

import (
  "fmt"
  "net"
)

type Server struct {
  Port uint
  Target HttPurr
  IPv4 net.Listener
  IPv6 net.Listener
  Clients []*Proxy
  Mutex *Semaphore
}

type ServerMap map[uint]*Server

func newServer(url string) (*Server, error) {
  // Retrieve the remote host and generate the upgrade request
  target, err := newHttPurr(url)
  if err != nil {
    return &Server{}, err
  }

  // Listen on IPv4 on a randomly assigned port
  v4, err := net.Listen("tcp4", "127.0.0.1:0")
  if err != nil {
    return &Server{}, err
  }

  // Determine the randomly assigned port
  port := uint(v4.Addr().(*net.TCPAddr).Port)

  // Listen on IPv6 on the same port
  v6, err := net.Listen("tcp6", fmt.Sprintf("[::1]:%d", port))
  if err != nil {
    v4.Close()
    return &Server{}, err
  }

  // Create the data structure representing the server
  server := &Server{
    IPv4: v4,
    IPv6: v6,
    Clients: []*Proxy{},
    Target: *target,
    Port: port,
    Mutex: newSemaphore(2),
  }

  // Accept incoming connections separately
  go server.accept()
  return server, nil
}

func (server *Server) Close() {
  // Close the listening servers first
  server.IPv4.Close()
  server.IPv6.Close()

  server.Mutex.Lock(2)
  // Disconnect the connected clients
  for _, client := range server.Clients {
    client.Close()
  }
  server.Clients = []*Proxy{}
  server.Mutex.Unlock(2)

}

func (server *Server) accept() {
  // Separate goroutine for the IPv6 clients
  go func(){
    defer server.Close()

    for {
      // FIXME: create the proxy asynchronously to reduce the size of the critical section
      server.Mutex.Lock(1)

      client, err := server.IPv6.Accept()
      if (err != nil) {
        server.Mutex.Unlock(1)
        break
      }

      proxy, err := newProxy(client, &server.Target)
      if err != nil {
        server.Mutex.Unlock(1)
        break;
      }

      server.Clients = append(server.Clients, proxy)
      server.Mutex.Unlock(1)
    }
  }()

  // Close the server if this method exits
  defer server.Close()

  for {
    server.Mutex.Lock(1)
    // Wait for an incoming connection
    client, err := server.IPv4.Accept()
    if (err != nil) {
      server.Mutex.Unlock(1)
      break
    }
    // Create the proxy from using clien
    proxy, err := newProxy(client, &server.Target)
    if err != nil {
      server.Mutex.Unlock(1)
      break;
    }
    // Append the proxy to the list of clients
    server.Clients = append(server.Clients, proxy)
    server.Mutex.Unlock(1)
  }
}