func handleGetEnviron(ctx *web.Context) (map[string]string, error) { if err := errorIfNotMaster(ctx); err != nil { return nil, err } ctx.ContentType("json") s := ctx.User.(*server) return s.session.Environ(), nil }
// List of files nice for tab completion func handleGetFiles(ctx *web.Context) error { if err := errorIfNotMaster(ctx); err != nil { return err } ctx.ContentType("json") paths, err := filepath.Glob(ctx.Params["pattern"]) if err != nil { return err } if paths == nil { paths = []string{} } return json.NewEncoder(ctx).Encode(paths) }
func handleGetCmdJson(ctx *web.Context, idstr string) error { id, _ := liblush.ParseCmdId(idstr) s := ctx.User.(*server) c := s.session.GetCommand(id) if c == nil { return web.WebError{404, "no such command: " + idstr} } md, err := metacmd{c}.Metadata() if err != nil { return err } ctx.ContentType("json") // Don't hammer me, but don't cache for too long. This resource is not // intended for polling, anyway. This may seem race sensitive, but that's // because it is. Only matters in big, multi user setups with lots of // concurrent changes, which is totally not lush's current intended use // case. So a few race conditions here and there are no biggy (for now). ctx.Response.Header().Set("cache-control", "max-age=3") return json.NewEncoder(ctx).Encode(md) }
func handleGetCmdidsJson(ctx *web.Context) error { s := ctx.User.(*server) ids := s.session.GetCommandIds() ctx.ContentType("json") return json.NewEncoder(ctx).Encode(ids) }
// Websocket control connection. All connected clients are considered equal. func handleWsCtrl(ctx *web.Context) error { wsconn, err := websocket.Upgrade(ctx.Response, ctx.Request, nil, 1024, 1024) if _, ok := err.(websocket.HandshakeError); ok { // Get the secret token to include in a websocket request ctx.ContentType("txt") fmt.Fprint(ctx.Response, getWebsocketKey()) return nil } else if err != nil { return err } s := ctx.User.(*server) ws := newWsClient(wsconn) defer ws.Close() // This is just for the incoming key, after which blocking on read is fine err = ws.SetReadDeadline(time.Now().Add(5 * time.Second)) if err != nil { return fmt.Errorf("Couldn't set read deadline for websocket: %v", err) } // First order of business: see if the client sends me the correct key // this could be done lots of ways different, perhaps more elegant ways: // HTTP headers, query parameters, secret path, ... BUT! This method is very // straight-forward and easy to understand. msg, err := ws.ReadTextMessage() if err != nil { return err } if string(msg) != getWebsocketKey() { // This is a best effort service to help whoever tried to connect to // this in debugging, hence no error checking. fmt.Fprint(ws, "Invalid lush key") return errors.New("Illegal websocket key") } // Alright I trust this client now, read ops are expected to block err = ws.SetReadDeadline(time.Time{}) if err != nil { return fmt.Errorf("Couldn't remove read deadline for websocket: %v", err) } // tell the client about its Id _, err = fmt.Fprint(ws, "clientid;", ws.Id) if err != nil { return fmt.Errorf("Websocket write error: %v", err) } // Subscribe this ws client to all future control events. Will be removed // automatically when the first Write fails (FlexibleMultiWriter). // Therefore, no need to worry about removing: client disconnects -> next // Write fails -> removed. s.ctrlclients.AddWriter(ws) // notify all other clients that a new client has connected wseventAllclients(s, "") // pretend somebody generated this event // TODO: keep clients updated about disconnects, too ws.isMaster = claimMaster(ctx) for { msg, err := ws.ReadTextMessage() if err != nil { s.ctrlclients.RemoveWriter(ws) return err } err = parseAndHandleWsEvent(s, ws, msg) if err != nil { return fmt.Errorf("error handling WS event: %v", err) } } return errors.New("unreachable") }
func process(ctx *web.Context) string { ctx.ContentType("txt") return fmt.Sprintf("%#v", ctx.Params) }