| package main |
| |
| import ( |
| "context" |
| "flag" |
| "fmt" |
| "html/template" |
| "net" |
| "net/http" |
| "math/rand" |
| "strings" |
| "time" |
| |
| "github.com/golang/glog" |
| |
| mirko "code.hackerspace.pl/hscloud/go/mirko" |
| "code.hackerspace.pl/hscloud/hswaw/laserproxy/tpl" |
| ) |
| |
| var ( |
| flagLaserAddress = "10.11.0.10" |
| flagLaserNetworkAddress = "10.11.0.1" |
| flagClientNetworkAddress = "10.8.1.2" |
| flagWebAddress = "127.0.0.1:8080" |
| flagOwnershipDuration = "1h" |
| |
| tplIndex = template.Must(template.New("index.html").Parse(tpl.MustAssetString("index.html"))) |
| ) |
| |
| type service struct { |
| lockCtrl chan *lockCtrl |
| } |
| |
| func main() { |
| flag.StringVar(&flagLaserAddress, "laser_address", flagLaserAddress, "Address of Ruida controller on laser network") |
| flag.StringVar(&flagLaserNetworkAddress, "laser_network", flagLaserNetworkAddress, "Address of proxy on laser network") |
| flag.StringVar(&flagClientNetworkAddress, "client_network", flagClientNetworkAddress, "Address of proxy on client network") |
| flag.StringVar(&flagWebAddress, "web_address", flagWebAddress, "Address and port to listen on for public web connections") |
| flag.StringVar(&flagOwnershipDuration, "timeout_duration", flagOwnershipDuration, "Time after which lasercutter lock will expire when not in use") |
| |
| flag.Parse() |
| m := mirko.New() |
| if err := m.Listen(); err != nil { |
| glog.Exitf("Could not listen: %v", err) |
| } |
| |
| lisLaser, err := net.ListenPacket("udp", fmt.Sprintf("%s:40200", flagLaserNetworkAddress)) |
| if err != nil { |
| glog.Fatalf("could not listen on laser network: %v", err) |
| } |
| defer lisLaser.Close() |
| |
| lisClient, err := net.ListenPacket("udp", fmt.Sprintf("%s:50200", flagClientNetworkAddress)) |
| if err != nil { |
| glog.Fatalf("could not listen on client network: %v", err) |
| } |
| defer lisClient.Close() |
| |
| laserIP := net.ParseIP(flagLaserAddress) |
| if laserIP == nil { |
| glog.Fatalf("could not parse laser IP address %q", flagLaserAddress) |
| } |
| laserAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:50200", laserIP.String())) |
| if err != nil { |
| glog.Fatalf("could not make laser UDP address: %v", err) |
| } |
| |
| ownershipDuration, err := time.ParseDuration(flagOwnershipDuration) |
| if err != nil { |
| glog.Fatalf("could not parse timeout duration: %v", err) |
| } |
| |
| ctx := m.Context() |
| s := &service{ |
| lockCtrl: make(chan *lockCtrl), |
| } |
| updates := make(chan *lockUpdate) |
| go s.runProxy(ctx, updates, laserAddr, lisLaser, lisClient) |
| go s.runLocker(ctx, s.lockCtrl, ownershipDuration) |
| s.lockCtrl <- &lockCtrl{ |
| subscribe: &lockCtrlSubscribe{ |
| subscriber: updates, |
| }, |
| } |
| |
| mux := http.NewServeMux() |
| mux.HandleFunc("/", s.handlerIndex) |
| mux.HandleFunc("/take", s.handlerTake) |
| mux.HandleFunc("/release", s.handlerRelease) |
| mux.HandleFunc("/force", s.handlerForce) |
| httpSrv := &http.Server{Addr: flagWebAddress, Handler: mux} |
| |
| glog.Infof("Listening for web connections on %q...", flagWebAddress) |
| go func() { |
| if err := httpSrv.ListenAndServe(); err != nil { |
| glog.Error(err) |
| } |
| }() |
| |
| if err := m.Serve(); err != nil { |
| glog.Exitf("Could not run: %v", err) |
| } |
| |
| glog.Info("Running.") |
| <-m.Done() |
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
| defer cancel() |
| httpSrv.Shutdown(ctx) |
| } |
| |
| var ( |
| sampleNames = []string{ |
| "enleth", "radex", "qdot", "hans acker", "lars aser", "makłowicz", |
| } |
| ) |
| |
| func remoteAddr(r *http.Request) string { |
| if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" { |
| return strings.Split(fwd, ":")[0] |
| } |
| return strings.Split(r.RemoteAddr, ":")[0] |
| } |
| |
| func (s *service) handlerIndex(w http.ResponseWriter, r *http.Request) { |
| res := make(chan *lockResCurrent) |
| s.lockCtrl <- &lockCtrl{ |
| getCurrent: &lockCtrlGetCurrent{ |
| res: res, |
| }, |
| } |
| cur := <-res |
| |
| err := tplIndex.Execute(w, struct { |
| You bool |
| CurrentAddress string |
| CurrentName string |
| CurrentDeadline string |
| SampleName string |
| }{ |
| You: cur.addr == remoteAddr(r), |
| CurrentAddress: cur.addr, |
| CurrentName: cur.note, |
| CurrentDeadline: fmt.Sprintf("%d minute(s)", int(cur.deadline.Sub(time.Now()).Minutes())), |
| SampleName: sampleNames[rand.Intn(len(sampleNames))], |
| }) |
| if err != nil { |
| glog.Errorf("rendering template: %v", err) |
| } |
| } |
| |
| func (s *service) handlerTake(w http.ResponseWriter, r *http.Request) { |
| if r.Method != "POST" { |
| return |
| } |
| r.ParseForm() |
| who := r.Form.Get("who") |
| prev := r.Form.Get("prev") |
| if who == "" { |
| fmt.Fprintf(w, "excuse me, who are you? please specify a name") |
| return |
| } |
| |
| res := make(chan bool) |
| take := &lockCtrlTake{ |
| note: who, |
| addr: remoteAddr(r), |
| prev: prev, |
| res: res, |
| } |
| s.lockCtrl <- &lockCtrl{ |
| take: take, |
| } |
| won := <-res |
| |
| if won { |
| http.Redirect(w, r, "/", 302) |
| } else { |
| fmt.Fprintf(w, "lock not taken") |
| } |
| } |
| |
| func (s *service) handlerRelease(w http.ResponseWriter, r *http.Request) { |
| if r.Method != "POST" { |
| return |
| } |
| res := make(chan struct{}) |
| s.lockCtrl <- &lockCtrl{ |
| release: &lockCtrlRelease{ |
| addr: remoteAddr(r), |
| res: res, |
| }, |
| } |
| <-res |
| |
| http.Redirect(w, r, "/", 302) |
| } |
| |
| func (s *service) handlerForce(w http.ResponseWriter, r *http.Request) { |
| if r.Method != "POST" { |
| return |
| } |
| res := make(chan struct{}) |
| s.lockCtrl <- &lockCtrl{ |
| release: &lockCtrlRelease{ |
| force: true, |
| res: res, |
| }, |
| } |
| <-res |
| |
| http.Redirect(w, r, "/", 302) |
| } |